Implement internal DSL for SemVer Expressions

This commit is contained in:
Zafar Khaja 2014-08-22 18:47:59 +03:00
parent ebb6737f13
commit e1aa90c2ec
5 changed files with 425 additions and 10 deletions

View File

@ -231,12 +231,26 @@ int result = v1.compareWithBuildsTo(v2); // < 0
SemVer Expressions API (Ranges)
----------------------
Since version 0.7.0 Java SemVer supports the SemVer Expressions API which is
implemented as an external DSL. The BNF grammar for the SemVer Expressions DSL
can be found in the corresponding issue
"[Implement the SemVer Expressions API](https://github.com/zafarkhaja/java-semver/issues/1)".
Java SemVer supports the SemVer Expressions API which is implemented as both
internal DSL and external DSL. The entry point for the API are
the `Version.satisfies` methods.
The entry point for the API is the `Version.satisfies` method.
### Internal DSL ###
The internal DSL is implemented by the `CompositeExpression` class using fluent
interface. For convenience, it also provides the `Helper` class with static
helper methods.
~~~ java
import com.github.zafarkhaja.semver.Version;
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
Version v = Version.valueOf("1.0.0-beta");
boolean result = v.satisfies(gte("1.0.0").and(lt("2.0.0"))); // false
~~~
### External DSL ###
The BNF grammar for the external DSL can be found in the corresponding
[issue](https://github.com/zafarkhaja/java-semver/issues/1).
~~~ java
import com.github.zafarkhaja.semver.Version;
@ -246,7 +260,7 @@ boolean result = v.satisfies(">=1.0.0 & <2.0.0"); // false
~~~
Below are examples of some common use cases, as well as syntactic sugar and some
other interesting capabilities of the SemVer Expressions DSL.
other interesting capabilities of the SemVer Expressions external DSL.
* Wildcard - `1.*` which is equivalent to `>=1.0.0 & <2.0.0`
* Tilde operator - `~1.5` which is equivalent to `>=1.5.0 & <2.0.0`
* Range - `1.0-2.0` which is equivalent to `>=1.0.0 & <=2.0.0`

View File

@ -308,11 +308,11 @@ public class Version implements Comparable<Version> {
}
/**
* Checks if this version satisfies the specified SemVer Expression.
* Checks if this version satisfies the specified SemVer Expression string.
*
* This method is a part of the SemVer Expressions API.
*
* @param expr the SemVer Expression
* @param expr the SemVer Expression string
* @return {@code true} if this version satisfies the specified
* SemVer Expression or {@code false} otherwise
* @throws ParseException in case of a general parse error
@ -322,7 +322,21 @@ public class Version implements Comparable<Version> {
*/
public boolean satisfies(String expr) {
Parser<Expression> parser = ExpressionParser.newInstance();
return parser.parse(expr).interpret(this);
return satisfies(parser.parse(expr));
}
/**
* Checks if this version satisfies the specified SemVer Expression.
*
* This method is a part of the SemVer Expressions API.
*
* @param expr the SemVer Expression
* @return {@code true} if this version satisfies the specified
* SemVer Expression or {@code false} otherwise
* @since 0.9.0
*/
public boolean satisfies(Expression expr) {
return expr.interpret(this);
}
/**

View File

@ -0,0 +1,266 @@
/*
* The MIT License
*
* Copyright 2012-2014 Zafar Khaja <zafarkhaja@gmail.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.ParseException;
import com.github.zafarkhaja.semver.UnexpectedCharacterException;
import com.github.zafarkhaja.semver.Version;
/**
* This class implements internal DSL for the
* SemVer Expressions using fluent interface.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.9.0
*/
public class CompositeExpression implements Expression {
/**
* A class with static helper methods.
*/
public static class Helper {
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Not} expression.
*
* @param expr an {@code Expression} to negate
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression not(Expression expr) {
return new CompositeExpression(new Not(expr));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Equal} expression.
*
* @param version a {@code Version} to check for equality
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression eq(Version version) {
return new CompositeExpression(new Equal(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Equal} expression.
*
* @param version a {@code Version} string to check for equality
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression eq(String version) {
return eq(Version.valueOf(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code NotEqual} expression.
*
* @param version a {@code Version} to check for non-equality
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression neq(Version version) {
return new CompositeExpression(new NotEqual(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code NotEqual} expression.
*
* @param version a {@code Version} string to check for non-equality
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression neq(String version) {
return neq(Version.valueOf(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Greater} expression.
*
* @param version a {@code Version} to compare with
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression gt(Version version) {
return new CompositeExpression(new Greater(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Greater} expression.
*
* @param version a {@code Version} string to compare with
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression gt(String version) {
return gt(Version.valueOf(version));
}
/**
* Creates a {@code CompositeExpression} with an
* underlying {@code GreaterOrEqual} expression.
*
* @param version a {@code Version} to compare with
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression gte(Version version) {
return new CompositeExpression(new GreaterOrEqual(version));
}
/**
* Creates a {@code CompositeExpression} with an
* underlying {@code GreaterOrEqual} expression.
*
* @param version a {@code Version} string to compare with
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression gte(String version) {
return gte(Version.valueOf(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Less} expression.
*
* @param version a {@code Version} to compare with
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression lt(Version version) {
return new CompositeExpression(new Less(version));
}
/**
* Creates a {@code CompositeExpression} with
* an underlying {@code Less} expression.
*
* @param version a {@code Version} string to compare with
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression lt(String version) {
return lt(Version.valueOf(version));
}
/**
* Creates a {@code CompositeExpression} with an
* underlying {@code LessOrEqual} expression.
*
* @param version a {@code Version} to compare with
* @return a newly created {@code CompositeExpression}
*/
public static CompositeExpression lte(Version version) {
return new CompositeExpression(new LessOrEqual(version));
}
/**
* Creates a {@code CompositeExpression} with an
* underlying {@code LessOrEqual} expression.
*
* @param version a {@code Version} string to compare with
* @return a newly created {@code CompositeExpression}
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public static CompositeExpression lte(String version) {
return lte(Version.valueOf(version));
}
}
/**
* The underlying expression tree.
*/
private Expression expr;
/**
* Constructs a {@code CompositeExpression}
* with an underlying {@code Expression}.
*
* @param expr the underlying expression
*/
public CompositeExpression(Expression expr) {
this.expr = expr;
}
/**
* Adds another {@code Expression} to {@code CompositeExpression}
* using {@code And} logical expression.
*
* @param expr an expression to add
* @return this {@code CompositeExpression}
*/
public CompositeExpression and(Expression expr) {
this.expr = new And(this.expr, expr);
return this;
}
/**
* Adds another {@code Expression} to {@code CompositeExpression}
* using {@code Or} logical expression.
*
* @param expr an expression to add
* @return this {@code CompositeExpression}
*/
public CompositeExpression or(Expression expr) {
this.expr = new Or(this.expr, expr);
return this;
}
/**
* Interprets the expression.
*
* @param version a {@code Version} string to interpret against
* @return the result of the expression interpretation
* @throws IllegalArgumentException if the input string is {@code NULL} or empty
* @throws ParseException when invalid version string is provided
* @throws UnexpectedCharacterException is a special case of {@code ParseException}
*/
public boolean interpret(String version) {
return interpret(Version.valueOf(version));
}
/**
* {@inheritDoc}
*/
@Override
public boolean interpret(Version version) {
return expr.interpret(version);
}
}

View File

@ -26,6 +26,7 @@ package com.github.zafarkhaja.semver;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
import static org.junit.Assert.*;
/**
@ -307,11 +308,18 @@ public class VersionTest {
}
@Test
public void shouldCheckIfVersionSatisfiesExpression() {
public void shouldCheckIfVersionSatisfiesExpressionString() {
Version v = Version.valueOf("2.0.0-beta");
assertTrue(v.satisfies("~1.0"));
assertFalse(v.satisfies(">=2.0 & <3.0"));
}
@Test
public void shouldCheckIfVersionSatisfiesExpression() {
Version v = Version.valueOf("2.0.0-beta");
assertTrue(v.satisfies(gte("1.0.0").and(lt("2.0.0"))));
assertFalse(v.satisfies(gte("2.0.0").and(lt("3.0.0"))));
}
}
public static class EqualsMethodTest {

View File

@ -0,0 +1,113 @@
/*
* The MIT License
*
* Copyright 2012-2014 Zafar Khaja <zafarkhaja@gmail.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.zafarkhaja.semver.expr;
import org.junit.Test;
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
import static org.junit.Assert.*;
/**
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
*/
public class CompositeExpressionTest {
@Test
public void shouldSupportEqualExpression() {
assertTrue(eq("1.0.0").interpret("1.0.0"));
assertFalse(eq("1.0.0").interpret("2.0.0"));
}
@Test
public void shouldSupportNotEqualExpression() {
assertTrue(neq("1.0.0").interpret("2.0.0"));
}
@Test
public void shouldSupportGreaterExpression() {
assertTrue(gt("1.0.0").interpret("2.0.0"));
assertFalse(gt("2.0.0").interpret("1.0.0"));
}
@Test
public void shouldSupportGreaterOrEqualExpression() {
assertTrue(gte("1.0.0").interpret("1.0.0"));
assertTrue(gte("1.0.0").interpret("2.0.0"));
assertFalse(gte("2.0.0").interpret("1.0.0"));
}
@Test
public void shouldSupportLessExpression() {
assertTrue(lt("2.0.0").interpret("1.0.0"));
assertFalse(lt("1.0.0").interpret("2.0.0"));
}
@Test
public void shouldSupportLessOrEqualExpression() {
assertTrue(lte("1.0.0").interpret("1.0.0"));
assertTrue(lte("2.0.0").interpret("1.0.0"));
assertFalse(lte("1.0.0").interpret("2.0.0"));
}
@Test
public void shouldSupportNotExpression() {
assertTrue(not(eq("1.0.0")).interpret("2.0.0"));
assertFalse(not(eq("1.0.0")).interpret("1.0.0"));
}
@Test
public void shouldSupportAndExpression() {
assertTrue(gt("1.0.0").and(lt("2.0.0")).interpret("1.5.0"));
assertFalse(gt("1.0.0").and(lt("2.0.0")).interpret("2.5.0"));
}
@Test
public void shouldSupportOrExpression() {
assertTrue(lt("1.0.0").or(gt("1.0.0")).interpret("1.5.0"));
assertFalse(gt("1.0.0").or(gt("2.0.0")).interpret("0.5.0"));
}
@Test
public void shouldSupportComplexExpressions() {
/* ((>=1.0.1 & <2) | (>=3.0 & <4)) & ((1-1.5) & (~1.5)) */
CompositeExpression expr =
gte("1.0.1").and(
lt("2.0.0").or(
gte("3.0.0").and(
lt("4.0.0").and(
gte("1.0.0").and(
lte("1.5.0").and(
gte("1.5.0").and(
lt("2.0.0")
)
)
)
)
)
)
);
assertTrue(expr.interpret("1.5.0"));
assertFalse(expr.interpret("2.5.0"));
}
}