diff --git a/README.md b/README.md index 38b9cbc..e5a1251 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ Java SemVer v0.7.0 (SemVer 2) [![Build Status](https://travis-ci.org/zafarkhaja/java-semver.png)](https://travis-ci.org/zafarkhaja/java-semver) -================== +============================= Java SemVer is a Java implementation of the Semantic Versioning Specification (http://semver.org/). - -Versioning ----------- +### Versioning ### Java SemVer is versioned according to the SemVer Specification. **NOTE**: The current release of the Java SemVer library has a major version of @@ -19,13 +17,13 @@ Usage Below are some common use cases for the Java SemVer library. ### Creating Versions ### -Java SemVer library is composed of one small package which contains a few -classes. All the classes but one are package-private and are not accessible -outside the package. The only public class is `Version` which acts as a -_facade_ for the client code. By design, the `Version` class is made immutable -by making its constructor package-private, so that it can not be subclassed or -directly instantiated. Instead of public constructor, the `Version` class -provides a _static factory method_, `Version.valueOf(String value)`. +The main class of the Java SemVer library is `Version` which implements the +Facade design pattern. By design, the `Version` class is made immutable by +making its constructors package-private, so that it can not be subclassed or +directly instantiated. Instead of public constructors, the `Version` class +provides few _static factory methods_. + +One of the methods is the `Version.valueOf` method. ```java import com.github.zafarkhaja.semver.Version; @@ -43,6 +41,17 @@ String build = v.getBuildMetadata(); // "build.1" String str = v.toString(); // "1.0.0-rc.1+build.1" ``` +The other static factory method is `Version.forIntegers` which is also +overloaded to allow fewer arguments. + +```java +import com.github.zafarkhaja.semver.Version; + +Version v1 = Version.forIntegers(1); +Version v2 = Version.forIntegers(1, 2); +Version v3 = Version.forIntegers(1, 2, 3); +``` + Another way to create a `Version` is to use a _builder_ class `Version.Builder`. ```java @@ -76,15 +85,15 @@ import com.github.zafarkhaja.semver.Version; Version v1 = Version.valueOf("1.2.3"); -// Incrementing major version +// Incrementing the major version Version v2 = v1.incrementMajorVersion(); // "2.0.0" Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha" -// Incrementing minor version +// Incrementing the minor version Version v3 = v1.incrementMinorVersion(); // "1.3.0" Version v3 = v1.incrementMinorVersion("alpha"); // "1.3.0-alpha" -// Incrementing patch version +// Incrementing the patch version Version v4 = v1.incrementPatchVersion(); // "1.2.4" Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha" @@ -92,30 +101,32 @@ Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha" String str = v1.toString(); // "1.2.3" ``` -There are also incrementor methods for pre-release version and build metadata. +There are also incrementor methods for the pre-release version and the build +metadata. ```java import com.github.zafarkhaja.semver.Version; -// Incrementing pre-release version +// Incrementing the pre-release version Version v1 = Version.valueOf("1.2.3-rc"); // considered as "rc.0" Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-rc.1" Version v3 = v2.incrementPreReleaseVersion(); // "1.2.3-rc.2" -// Incrementing build metadata +// Incrementing the build metadata Version v1 = Version.valueOf("1.2.3-rc+build"); // considered as "build.0" Version v2 = v1.incrementBuildMetadata(); // "1.2.3-rc+build.1" Version v3 = v2.incrementBuildMetadata(); // "1.2.3-rc+build.2" ``` -When incrementing normal or pre-release versions build metadata is always dropped. +When incrementing the normal or pre-release versions the build metadata is +always dropped. ```java import com.github.zafarkhaja.semver.Version; Version v1 = Version.valueOf("1.2.3-beta+build"); -// Incrementing normal version +// Incrementing the normal version Version v2 = v1.incrementMajorVersion(); // "2.0.0" Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha" @@ -125,7 +136,7 @@ Version v3 = v1.incrementMinorVersion("alpha"); // "1.3.0-alpha" Version v4 = v1.incrementPatchVersion(); // "1.2.4" Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha" -// Incrementing pre-release version +// Incrementing the pre-release version Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-beta.1" ``` **NOTE**: The discussion page https://github.com/mojombo/semver/issues/60 might @@ -134,8 +145,8 @@ incrementor methods. ### Comparing Versions ### Comparing versions with Java SemVer is easy. The `Version` class implements the -`Comparable` interface, it also overrides the `Object.equals(Object obj)` method -and provides some more methods for convenient comparing. +`Comparable` interface, it also overrides the `Object.equals` method and provides +some more methods for convenient comparing. ```java import com.github.zafarkhaja.semver.Version; @@ -152,7 +163,7 @@ boolean result = v1.lessThan(v2); // true boolean result = v1.lessThanOrEqualTo(v2); // true ``` -When determining version precedence build metadata is ignored (SemVer p.10). +When determining version precedence the build metadata is ignored (SemVer p.10). ```java import com.github.zafarkhaja.semver.Version; @@ -164,9 +175,9 @@ int result = v1.compareTo(v2); // = 0 boolean result = v1.equals(v2); // true ``` -Sometimes, however, you might want to compare versions with build metadata in -mind. For such cases Java SemVer provides a _comparator_ `Version.BUILD_AWARE_ORDER` -and a convenience method `Version.compareWithBuildsTo(Version other)`. +Sometimes, however, you might want to compare versions with the build metadata +in mind. For such cases Java SemVer provides a _comparator_ `Version.BUILD_AWARE_ORDER` +and a convenience method `Version.compareWithBuildsTo`. ```java import com.github.zafarkhaja.semver.Version; @@ -181,9 +192,34 @@ boolean result = v1.equals(v2); // false ``` +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)". + +The entry point for the API is the `Version.satisfies` method. + +```java +import com.github.zafarkhaja.semver.Version; + +Version v = Version.valueOf("1.0.0-beta"); +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. +* 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` +* Negation operator - `!(1.*)` which is equivalent to `<1.0.0 & >=2.0.0` +* Short notation - `1` which is equivalent to `=1.0.0` +* Parenthesized expression - `~1.3 | (1.4.* & !=1.4.5) | ~2` + + TODO ---- -* [Implement ranges](https://github.com/zafarkhaja/java-semver/issues/1) * [Write doc comments for all API classes and methods](https://github.com/zafarkhaja/java-semver/issues/2) diff --git a/src/main/java/com/github/zafarkhaja/semver/Version.java b/src/main/java/com/github/zafarkhaja/semver/Version.java index 40e1839..30a2db1 100644 --- a/src/main/java/com/github/zafarkhaja/semver/Version.java +++ b/src/main/java/com/github/zafarkhaja/semver/Version.java @@ -23,6 +23,8 @@ */ package com.github.zafarkhaja.semver; +import com.github.zafarkhaja.semver.expr.Expression; +import com.github.zafarkhaja.semver.expr.ExpressionParser; import java.util.Comparator; /** @@ -116,6 +118,23 @@ public class Version implements Comparable { return VersionParser.parseValidSemVer(version); } + public static Version forIntegers(int major) { + return new Version(new NormalVersion(major, 0, 0)); + } + + public static Version forIntegers(int major, int minor) { + return new Version(new NormalVersion(major, minor, 0)); + } + + public static Version forIntegers(int major, int minor, int patch) { + return new Version(new NormalVersion(major, minor, patch)); + } + + public boolean satisfies(String expr) { + Parser parser = ExpressionParser.newInstance(); + return parser.parse(expr).interpret(this); + } + public Version incrementMajorVersion() { return new Version(normal.incrementMajor()); } diff --git a/src/main/java/com/github/zafarkhaja/semver/expr/ExpressionParser.java b/src/main/java/com/github/zafarkhaja/semver/expr/ExpressionParser.java new file mode 100644 index 0000000..e403bb2 --- /dev/null +++ b/src/main/java/com/github/zafarkhaja/semver/expr/ExpressionParser.java @@ -0,0 +1,222 @@ +/* + * The MIT License + * + * Copyright 2013 Zafar Khaja . + * + * 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.Parser; +import com.github.zafarkhaja.semver.Version; +import com.github.zafarkhaja.semver.expr.Lexer.Token; +import com.github.zafarkhaja.semver.util.Stream; +import com.github.zafarkhaja.semver.util.Stream.ElementType; +import java.util.EnumSet; +import java.util.Iterator; +import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*; + +/** + * + * @author Zafar Khaja + */ +public class ExpressionParser implements Parser { + + private final Lexer lexer; + private Stream tokens; + + ExpressionParser(Lexer lexer) { + this.lexer = lexer; + } + + public static Parser newInstance() { + return new ExpressionParser(new Lexer()); + } + + @Override + public Expression parse(String input) { + tokens = lexer.tokenize(input); + return parseSemVerExpression(); + } + + private Expression parseSemVerExpression() { + Expression expr; + if (tokens.positiveLookahead(NOT)) { + tokens.consume(); + tokens.consume(LEFT_PAREN); + expr = new Not(parseSemVerExpression()); + tokens.consume(RIGHT_PAREN); + } else if (tokens.positiveLookahead(LEFT_PAREN)) { + tokens.consume(LEFT_PAREN); + expr = parseSemVerExpression(); + tokens.consume(RIGHT_PAREN); + } else { + expr = parseExpression(); + } + return parseBooleanExpression(expr); + } + + private Expression parseBooleanExpression(Expression expr) { + if (tokens.positiveLookahead(AND)) { + tokens.consume(); + expr = new And(expr, parseSemVerExpression()); + } else if (tokens.positiveLookahead(OR)) { + tokens.consume(); + expr = new Or(expr, parseSemVerExpression()); + } + return expr; + } + + private Expression parseExpression() { + if (tokens.positiveLookahead(TILDE)) { + return parseTildeExpression(); + } else if (isVersionExpression()) { + return parseVersionExpression(); + } else if (isRangeExpression()) { + return parseRangeExpression(); + } + return parseComparisonExpression(); + } + + private Expression parseComparisonExpression() { + Token token = tokens.lookahead(); + Expression expr; + switch (token.type) { + case EQUAL: + tokens.consume(); + expr = new Equal(parseVersion()); + break; + case NOT_EQUAL: + tokens.consume(); + expr = new NotEqual(parseVersion()); + break; + case GREATER: + tokens.consume(); + expr = new Greater(parseVersion()); + break; + case GREATER_EQUAL: + tokens.consume(); + expr = new GreaterOrEqual(parseVersion()); + break; + case LESS: + tokens.consume(); + expr = new Less(parseVersion()); + break; + case LESS_EQUAL: + tokens.consume(); + expr = new LessOrEqual(parseVersion()); + break; + default: + expr = new Equal(parseVersion()); + } + return expr; + } + + private Expression parseTildeExpression() { + tokens.consume(TILDE); + int major = intOf(tokens.consume(NUMERIC).lexeme); + if (!tokens.positiveLookahead(DOT)) { + return new GreaterOrEqual(versionOf(major, 0, 0)); + } + tokens.consume(DOT); + int minor = intOf(tokens.consume(NUMERIC).lexeme); + if (!tokens.positiveLookahead(DOT)) { + return new And( + new GreaterOrEqual(versionOf(major, minor, 0)), + new Less(versionOf(major + 1, 0, 0)) + ); + } + tokens.consume(DOT); + int patch = intOf(tokens.consume(NUMERIC).lexeme); + return new And( + new GreaterOrEqual(versionOf(major, minor, patch)), + new Less(versionOf(major, minor + 1, 0)) + ); + } + + private boolean isVersionExpression() { + return isVersionFollowedBy(STAR); + } + + private Expression parseVersionExpression() { + int major = intOf(tokens.consume(NUMERIC).lexeme); + tokens.consume(DOT); + if (tokens.positiveLookahead(STAR)) { + tokens.consume(); + return new And( + new GreaterOrEqual(versionOf(major, 0, 0)), + new Less(versionOf(major + 1, 0, 0)) + ); + } + int minor = intOf(tokens.consume(NUMERIC).lexeme); + tokens.consume(DOT); + tokens.consume(STAR); + return new And( + new GreaterOrEqual(versionOf(major, minor, 0)), + new Less(versionOf(major, minor + 1, 0)) + ); + } + + private boolean isRangeExpression() { + return isVersionFollowedBy(HYPHEN); + } + + private Expression parseRangeExpression() { + Expression ge = new GreaterOrEqual(parseVersion()); + tokens.consume(HYPHEN); + Expression le = new LessOrEqual(parseVersion()); + return new And(ge, le); + } + + private Version parseVersion() { + int major = intOf(tokens.consume(NUMERIC).lexeme); + int minor = 0; + if (tokens.positiveLookahead(DOT)) { + tokens.consume(); + minor = intOf(tokens.consume(NUMERIC).lexeme); + } + int patch = 0; + if (tokens.positiveLookahead(DOT)) { + tokens.consume(); + patch = intOf(tokens.consume(NUMERIC).lexeme); + } + return versionOf(major, minor, patch); + } + + private boolean isVersionFollowedBy(ElementType type) { + EnumSet expected = EnumSet.of(NUMERIC, DOT); + Iterator it = tokens.iterator(); + Token lookahead = null; + while (it.hasNext()) { + lookahead = it.next(); + if (!expected.contains(lookahead.type)) { + break; + } + } + return type.isMatchedBy(lookahead); + } + + private Version versionOf(int major, int minor, int patch) { + return Version.forIntegers(major, minor, patch); + } + + private int intOf(String value) { + return Integer.parseInt(value); + } +} diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java index af65488..e993fa9 100644 --- a/src/test/java/com/github/zafarkhaja/semver/VersionTest.java +++ b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java @@ -305,6 +305,13 @@ public class VersionTest { assertTrue(0 == v1.compareTo(v2)); assertTrue(0 > v1.compareWithBuildsTo(v2)); } + + @Test + public void shouldCheckIfVersionSatisfiesExpression() { + Version v = Version.valueOf("2.0.0-beta"); + assertTrue(v.satisfies("~1.0")); + assertFalse(v.satisfies(">=2.0 & <3.0")); + } } public static class EqualsMethodTest { diff --git a/src/test/java/com/github/zafarkhaja/semver/expr/ExpressionParserTest.java b/src/test/java/com/github/zafarkhaja/semver/expr/ExpressionParserTest.java new file mode 100644 index 0000000..e8cdd65 --- /dev/null +++ b/src/test/java/com/github/zafarkhaja/semver/expr/ExpressionParserTest.java @@ -0,0 +1,205 @@ +/* + * The MIT License + * + * Copyright 2013 Zafar Khaja . + * + * 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.Version; +import com.github.zafarkhaja.semver.util.UnexpectedElementTypeException; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Zafar Khaja + */ +public class ExpressionParserTest { + + @Test + public void shouldParseEqualComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression eq = parser.parse("=1.0.0"); + assertTrue(eq.interpret(Version.valueOf("1.0.0"))); + } + + @Test + public void shouldParseEqualComparisonExpressionIfOnlyVersionGiven() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression eq = parser.parse("1.0.0"); + assertTrue(eq.interpret(Version.valueOf("1.0.0"))); + } + + @Test + public void shouldParseNotEqualComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression ne = parser.parse("!=1.0.0"); + assertTrue(ne.interpret(Version.valueOf("1.2.3"))); + } + + @Test + public void shouldParseGreaterComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression gt = parser.parse(">1.0.0"); + assertTrue(gt.interpret(Version.valueOf("1.2.3"))); + } + + @Test + public void shouldParseGreaterOrEqualComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression ge = parser.parse(">=1.0.0"); + assertTrue(ge.interpret(Version.valueOf("1.0.0"))); + assertTrue(ge.interpret(Version.valueOf("1.2.3"))); + } + + @Test + public void shouldParseLessComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression lt = parser.parse("<1.2.3"); + assertTrue(lt.interpret(Version.valueOf("1.0.0"))); + } + + @Test + public void shouldParseLessOrEqualComparisonExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression le = parser.parse("<=1.2.3"); + assertTrue(le.interpret(Version.valueOf("1.0.0"))); + assertTrue(le.interpret(Version.valueOf("1.2.3"))); + } + + @Test + public void shouldParseTildeExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr1 = parser.parse("~1"); + assertTrue(expr1.interpret(Version.valueOf("1.2.3"))); + assertTrue(expr1.interpret(Version.valueOf("3.2.1"))); + Expression expr2 = parser.parse("~1.2"); + assertTrue(expr2.interpret(Version.valueOf("1.2.3"))); + assertFalse(expr2.interpret(Version.valueOf("2.0.0"))); + Expression expr3 = parser.parse("~1.2.3"); + assertTrue(expr3.interpret(Version.valueOf("1.2.3"))); + assertFalse(expr3.interpret(Version.valueOf("1.3.0"))); + } + + @Test + public void shouldParseShortFormOfVersion() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr1 = parser.parse("1"); + assertTrue(expr1.interpret(Version.valueOf("1.0.0"))); + Expression expr2 = parser.parse("2.0"); + assertTrue(expr2.interpret(Version.valueOf("2.0.0"))); + } + + @Test + public void shouldParseVersionExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr1 = parser.parse("1.*"); + assertTrue(expr1.interpret(Version.valueOf("1.2.3"))); + assertFalse(expr1.interpret(Version.valueOf("3.2.1"))); + Expression expr2 = parser.parse("1.2.*"); + assertTrue(expr2.interpret(Version.valueOf("1.2.3"))); + assertFalse(expr2.interpret(Version.valueOf("1.3.2"))); + } + + @Test + public void shouldParseRangeExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression range = parser.parse("1.0.0 - 2.0.0"); + assertTrue(range.interpret(Version.valueOf("1.2.3"))); + assertFalse(range.interpret(Version.valueOf("3.2.1"))); + } + + @Test + public void shouldParseAndBooleanExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression and = parser.parse(">=1.0.0 & <2.0.0"); + assertTrue(and.interpret(Version.valueOf("1.2.3"))); + assertFalse(and.interpret(Version.valueOf("3.2.1"))); + } + + @Test + public void shouldParseOrBooleanExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression or = parser.parse("1.* | =2.0.0"); + assertTrue(or.interpret(Version.valueOf("1.2.3"))); + assertFalse(or.interpret(Version.valueOf("2.1.0"))); + } + + @Test + public void shouldParseParenthesizedExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr = parser.parse("(1)"); + assertTrue(expr.interpret(Version.valueOf("1.0.0"))); + assertFalse(expr.interpret(Version.valueOf("2.0.0"))); + } + + @Test + public void shouldParseExpressionWithMultipleParentheses() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr = parser.parse("((1))"); + assertTrue(expr.interpret(Version.valueOf("1.0.0"))); + assertFalse(expr.interpret(Version.valueOf("2.0.0"))); + } + + @Test + public void shouldParseNotExpression() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression not1 = parser.parse("!(1)"); + assertTrue(not1.interpret(Version.valueOf("2.0.0"))); + assertFalse(not1.interpret(Version.valueOf("1.0.0"))); + Expression not2 = parser.parse("0.* & !(>=1 & <2)"); + assertTrue(not2.interpret(Version.valueOf("0.5.0"))); + assertFalse(not2.interpret(Version.valueOf("1.0.1"))); + Expression not3 = parser.parse("!(>=1 & <2) & >=2"); + assertTrue(not3.interpret(Version.valueOf("2.0.0"))); + assertFalse(not3.interpret(Version.valueOf("1.2.3"))); + } + + @Test + public void shouldRespectPrecedenceWhenUsedWithParentheses() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr1 = parser.parse("(~1.0 & <2.0) | >2.0"); + assertTrue(expr1.interpret(Version.valueOf("2.5.0"))); + Expression expr2 = parser.parse("~1.0 & (<2.0 | >2.0)"); + assertFalse(expr2.interpret(Version.valueOf("2.5.0"))); + } + + @Test + public void shouldParseComplexExpressions() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + Expression expr = parser.parse( + "((>=1.0.1 & <2) | (>=3.0 & <4)) & ((1-1.5) & (~1.5))" + ); + assertTrue(expr.interpret(Version.valueOf("1.5.0"))); + assertFalse(expr.interpret(Version.valueOf("2.5.0"))); + } + + @Test + public void shouldRaiseErrorIfClosingParenthesisIsMissing() { + ExpressionParser parser = new ExpressionParser(new Lexer()); + try { + parser.parse("((>=1.0.1 & < 2)"); + } catch (UnexpectedElementTypeException e) { + return; + } + fail("Should raise error if closing parenthesis is missing"); + } +}