pax_global_header00006660000000000000000000000064145507330640014521gustar00rootroot0000000000000052 comment=75b5abe97ca55c4569ea84e09330db22a0df2db7 jsemver-0.10.2/000077500000000000000000000000001455073306400132545ustar00rootroot00000000000000jsemver-0.10.2/.editorconfig000066400000000000000000000003041455073306400157260ustar00rootroot00000000000000root = true [*] charset = utf-8 max_line_length = 100 end_of_line = lf indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true [pom.xml] indent_size = 2 jsemver-0.10.2/.gitignore000066400000000000000000000000461455073306400152440ustar00rootroot00000000000000/target/ /.idea/ /nb-configuration.xmljsemver-0.10.2/CHANGELOG.md000066400000000000000000000077541455073306400151020ustar00rootroot00000000000000Java SemVer Changelog ===================== ## v0.10.2 (Jan 14, 2024) ## * Fixed `Version.toBuilder()` bug ([#68](https://github.com/zafarkhaja/jsemver/issues/68)) ## v0.10.1 (Jan 12, 2024) ## * Fixed backward compatibility of `Version`'s getters ## v0.10.0 (Jan 10, 2024) ## * Made `Version` serializable ([#47](https://github.com/zafarkhaja/jsemver/issues/47)) * Enhanced `Version.Builder` class * Widened numeric identifiers to `long` * Replaced `Version`'s static factory methods with `Version.parse()` and `Version.of()` * Created `Version.tryParse()` method which returns `Optional` * Created `Version.isValid()` method ([#26](https://github.com/zafarkhaja/jsemver/issues/26)) * Updated version parser to accept partial versions, such as `1` or `1.2` ([#15](https://github.com/zafarkhaja/jsemver/issues/15)) * Renamed and enhanced `Version`'s getter methods * Renamed and enhanced `Version`'s "incrementor" methods * Changed `Version.satisfies(Expression)`'s parameter to `Predicate` * Created `Version.isPreRelease()` method * Created `Version.isStable()` method * Created `Version.isPublicApiStable()` method * Created `Version.isPublicApiCompatibleWith(Version)` method * Created methods to check versions' compatibility ([#21](https://github.com/zafarkhaja/jsemver/issues/21)) * Refactored `Version`'s comparators and "comparator" methods * Deprecated `Version.BUILD_AWARE_ORDER` comparator * Updated Range Expressions parser to support double-symbol `&&` and `||` operators ([#23](https://github.com/zafarkhaja/jsemver/issues/23)) * Fixed Exceptions' `getMessage()` method ([#38](https://github.com/zafarkhaja/jsemver/issues/38)) * Performed major code and documentation improvements * Fixed various bugs and warnings, improved stability * Added `Automatic-Module-Name` to MANIFEST for JPMS support * Upgraded Java support to 1.8 * Migrated to JUnit 5 * Added `CONTRIBUTING.md` * Added `.editorconfig` file * Included `LICENSE` in the JAR artifact ([#44](https://github.com/zafarkhaja/jsemver/issues/44)) * Improved deployment support * Updated project dependencies * Removed Travis CI integration ## v0.9.0 (Mar 19, 2015) ## * Implemented internal DSL for SemVer Expressions ([#6](https://github.com/zafarkhaja/jsemver/issues/6)) * Added support for Caret (^) and X-Ranges to SemVer Expressions ([#18](https://github.com/zafarkhaja/jsemver/pull/18)) * Improved Tilde ranges to be compatible with `node-semver` ([#18](https://github.com/zafarkhaja/jsemver/pull/18)) * Refactored and polished SemVer Expressions parser * Performed minor code improvements * Updated project dependencies ## v0.8.0 (Aug 18, 2014) ## * Implemented fluent interface for `Version.Builder` * Rearranged and refactored exceptions * Refactored version and SemVer Expressions parsers * Improved error handling and reporting in parsers ([#7](https://github.com/zafarkhaja/jsemver/issues/7)) * Performed minor code improvements and bug fixes * Enhanced Javadoc comments * Added "Exception Handling" section to `README.md` ## v0.7.2 (Dec 30, 2013) ## * Fixed `Version.hashCode()` bug ([#8](https://github.com/zafarkhaja/jsemver/issues/8)) ## v0.7.1 (Dec 01, 2013) ## * Fixed "unchecked generic array creation" warnings ([#5](https://github.com/zafarkhaja/jsemver/issues/5)) * Performed minor code and Javadoc improvements ## v0.7.0 (Nov 16, 2013) ## * Upgraded the library to comply with SemVer 2.0.0 * Replaced regular expressions with a parser * Implemented SemVer Expressions ([#1](https://github.com/zafarkhaja/jsemver/issues/1)) * Documented the source code with Javadoc ([#2](https://github.com/zafarkhaja/jsemver/issues/2)) * Published the library to Maven Central ([#4](https://github.com/zafarkhaja/jsemver/issues/4)) * Performed refactoring and minor code improvements * Updated and reformatted `README.md` * Added `CHANGELOG.md` ## Unpublished Versions (Jan 27 - Mar 31, 2013) ## * Implemented basic functionality in compliance with SemVer 2.0.0-rc.2 * Created `Version.Builder` class * Created `Version.BUILD_AWARE_ORDER` comparator * Integrated Travis CI jsemver-0.10.2/CONTRIBUTING.md000066400000000000000000000100341455073306400155030ustar00rootroot00000000000000Contribution Guidelines ======================= Thank you for your interest in contributing to the Java SemVer library. All contributions are welcome and appreciated. There are mainly 3 ways in which you can contribute to the project: 1. make code changes to add features, fix bugs, correct typos, refactor, etc. 2. open new issues to suggest improvements, request features, report bugs, etc. 3. help around the repository with other people's issues and pull requests New issues and pull requests can be submitted from the [project's home page](https://github.com/zafarkhaja/jsemver) on GitHub. Making Code Changes ------------------- To make code contribution go smoothly and to ensure consistency and conceptual integrity throughout the project there are some rules to keep in mind as you are making code changes. ### Plan Before making a code change, unless an obviously trivial one, it's highly recommended to open an issue to discuss the problem you are trying to solve, possible solutions and implementation details with maintainer(s). If you intend to work on an existing issue, inform other participants of the discussion about your intent to avoid situations where multiple similar pull requests get submitted. ### Develop To start coding fork the project and create a new topic branch. Under the current branching workflow topic branches of types feature, bugfix, etc. are branched off the latest release branch and hotfix branches are branched off the master branch. Each topic branch should only contain code that is relevant to that topic. Branches must be given meaningful names. It's a good idea to prefix it with a branch type (feature, bugfix, hotfix, support, etc.) and add the issue number, e.g. `feature/47-serializability`. When writing the code, adhere to the project's code style. The project doesn't follow any one particular code style, so use the existing code as a reference. The submitted code must follow the Clean Code principles. The submitted code must be covered with unit tests. Exercising TDD while writing the code is highly encouraged. If necessary, documentation must be also updated to reflect the code changes. Once you are ready to commit, strive for small and self-contained commits to logically group related code changes. Commits must have informative and well-formed messages (see `git log` for some examples). ### Submit Before submitting, synchronize your work with the upstream branch using `git rebase`. To submit your code create a pull request with a description of the change and a reference to the issue it's related to, if any. Once the pull request is reviewed to ensure adherence to the above rules and accepted, the topic branch will be merged by the maintainer(s). Unlike issues, pull requests should not be used to discuss the problem being solved and/or alternative solutions. Opening New Issues ------------------ In order to avoid duplicates make sure the subject isn't covered by any of the existing issues, open or closed, before opening a new one. When reporting a bug, provide as much context and relevant information as you can, such as affected version, expected result, actual result and steps to reproduce, ideally in the form of executable test case. When requesting a feature, provide as much context and relevant information as you can, such as a clear description of the feature and its use case, motivation behind it, possible implementations, code examples, etc. You can also use issues to request support or clarification. Helping Around -------------- Obviously writing code and opening new issues are not the only two ways in which you can contribute. You can also contribute by helping out with other people's issues and pull requests. You can provide support on issues by labeling them, verifying provided information, reproducing bugs, validating requests, answering support-related questions and participating in discussions. You can provide support on pull requests by reviewing and validating code changes submitted by others. And finally, you can help out by cleaning up stale issues and pull requests. jsemver-0.10.2/LICENSE000066400000000000000000000021111455073306400142540ustar00rootroot00000000000000The MIT License Copyright 2012-2024 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. jsemver-0.10.2/README.md000066400000000000000000000154061455073306400145410ustar00rootroot00000000000000Java SemVer v0.10.2 (SemVer 2) ============================== Java SemVer is a Java implementation of the Semantic Versioning Specification (http://semver.org/). ### Versioning ### Java SemVer is versioned according to the SemVer Specification. **NOTE**: The current version of the Java SemVer library has a major version of zero, which according to the SemVer p.4 means that the library is under initial development and its public API should not be considered stable. ### Contributing ### For the guidelines on how to report bugs, request features, submit code changes, and contribute in various other ways please refer to the `CONTRIBUTING.md` file. ### License ### Java SemVer is licensed under the MIT License - see the `LICENSE` file for details. Library Usage ------------- * [Installation](#installation) * [Common Use Cases](#common-use-cases) * [Creating Versions](#creating-versions) * [Incrementing Versions](#incrementing-versions) * [Comparing Versions](#comparing-versions) * [Range Expressions](#range-expressions) * [Exception Handling](#exception-handling) ## Installation ## To use the latest stable version add the following dependency to your project ~~~ xml com.github.zafarkhaja java-semver 0.10.2 ~~~ ## Common Use Cases ## The Java SemVer library is built around the `Version` class which represents version as defined by the SemVer Specification. The `Version` class contains methods for parsing version strings, incrementing obtained versions, checking their individual characteristics, comparing with each other and determining their relative precedence. Below are some common use cases of the `Version` class ~~~ java import com.github.zafarkhaja.semver.Version; ~~~ ### Creating Versions ### There are 3 ways to obtain a `Version` instance: 1. by using the `Version.parse()` or `Version.tryParse()` methods ~~~ java Version v = Version.parse("1.2.3-pre-release+build.metadata"); Optional o = Version.tryParse("1.2.3-pre-release+build.metadata"); ~~~ 2. by using the `Version.of()` method ~~~ java Version v = Version.of(1, 2, 3, "pre-release", "build.metadata"); ~~~ 3. by using the `Version.Builder` class ~~~ java Version v1 = new Version.Builder() // 0.0.0 .setVersionCore(0, 1) // 0.1.0 .setMajorVersion(1) // 1.1.0 .setPreReleaseVersion("beta") // 1.1.0-beta .addPreReleaseIdentifiers("1") // 1.1.0-beta.1 .setBuildMetadata("build", "1") // 1.1.0-beta.1+build.1 .unsetBuildMetadata() // 1.1.0-beta.1 .build() ; Version v2 = v1.toBuilder() // 1.1.0-beta.1 .setMinorVersion(2) // 1.2.0-beta.1 .setPatchVersion(3) // 1.2.3-beta.1 .unsetPreReleaseVersion() // 1.2.3 .build() ; ~~~ ### Incrementing Versions ### The `Version` class provides "incrementor" methods for incrementing normal and pre-release versions. Due to its nature as metadata, incrementing build metadata is not supported. ~~~ java Version v = Version.of(0, 1, 0) // 0.1.0 .nextPatchVersion() // 0.1.1 .nextMinorVersion() // 0.2.0 .withBuildMetadata("abcdefg") // 0.2.0+abcdefg .nextMajorVersion("beta") // 1.0.0-beta .nextPreReleaseVersion() // 1.0.0-beta.1 .nextPreReleaseVersion("rc", "1") // 1.0.0-rc.1 .nextPreReleaseVersion() // 1.0.0-rc.2 .toStableVersion() // 1.0.0 ; ~~~ ### Comparing Versions ### The recommended way to determine version precedence is to use "comparator" methods ~~~ java Version v1 = Version.of(1, 2, 3, "rc.1", "build.1"); Version v2 = Version.of(1, 2, 3, "rc.1", "build.2"); v1.isHigherThan(v2); // false v1.isHigherThanOrEquivalentTo(v2); // true v1.isLowerThan(v2); // false v1.isLowerThanOrEquivalentTo(v2); // true v1.isEquivalentTo(v2); // true ~~~ The other options, mainly intended for use in comparison-based data structures, are the `Version.INCREMENT_ORDER` comparator for "natural" ordering, and the `Version.PRECEDENCE_ORDER` comparator for highest precedence ordering, which is reverse of the former one. ~~~ java Version v1 = Version.of(1, 2, 3, "rc.1"); Version v2 = Version.of(1, 2, 3, "rc.2"); Version.INCREMENT_ORDER.compare(v1, v2); // < 0 -> [v1, v2] Version.PRECEDENCE_ORDER.compare(v1, v2); // > 0 -> [v2, v1] ~~~ **NOTE**: The `equals()` and `compareTo()` methods don't adhere to the Specification regarding build metadata, and therefore shouldn't be used for determining version precedence. Also, they are not really intended to be used directly, but rather by hash- and comparison-based data structures, respectively. That said, there are still cases where they can prove useful, like testing versions for exact equality or ordering them based on their build metadata. ~~~ java Version v1 = Version.of(1, 2, 3, null, "build.1"); Version v2 = Version.of(1, 2, 3, null, "build.2"); v1.isEquivalentTo(v2); // true v1.equals(v2); // false v1.isLowerThan(v2); // false v1.compareTo(v2); // < 0 ~~~ ## Range Expressions ## Java SemVer supports Range Expressions with an opinionated [BNF grammar](https://github.com/zafarkhaja/jsemver/issues/1). **NOTE**: The Java SemVer Range Expressions are not fully compatible with the `node-semver` ranges. ~~~ java Version v = Version.of(1, 2, 3, "pre-release"); v.satisfies(">=1.0.0 && <2.0.0"); // false ~~~ The following is the list of supported notations and their interpretations: * Wildcard Ranges (`*`|`X`|`x`): `1.*` interpreted as `>=1.0.0 && <2.0.0` * Tilde Ranges (`~`): `~1.5` interpreted as `>=1.5.0 && <1.6.0` * Hyphen Ranges (`-`): `1.0-2.0` interpreted as `>=1.0.0 && <=2.0.0` * Caret Ranges (`^`): `^0.2.3` interpreted as `>=0.2.3 && <0.3.0` * Partial Version Ranges: `1` interpreted as `1.x` or `>=1.0.0 && <2.0.0` * Negation operator: `!(1.x)` interpreted as `<1.0.0 && >=2.0.0` * Parenthesized expressions: `~1.3 || (1.4.* && !=1.4.5) || ~2` There is also an internal DSL available just in case... ~~~ java import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*; Version v = Version.of(1, "beta"); v.satisfies(gte(Version.of(1)).and(lt(Version.of(2)))); // false ~~~ ## Exception Handling ## These are the exceptions you can expect when working with the `Version` class: * `ArithmeticException` is thrown if increment operation causes numeric identifier overflow * `IllegalArgumentException`, depending on the method and the parameter type, is thrown if you pass a `null` reference, an empty `String`, or a negative number * `IllegalStateException` is thrown in situations when a certain method call is unexpected, like incrementing a pre-release version of a stable version * `ParseException` and its subtypes are thrown if the specified string argument can't be parsed jsemver-0.10.2/pom.xml000066400000000000000000000115201455073306400145700ustar00rootroot00000000000000 4.0.0 com.github.zafarkhaja java-semver 0.10.2 jar Java SemVer Java implementation of the SemVer Specification https://github.com/zafarkhaja/jsemver The MIT License http://www.opensource.org/licenses/mit-license.php repo zafarkhaja Zafar Khaja zafarkhaja@gmail.com +3 https://github.com/zafarkhaja/jsemver scm:git:git://github.com/zafarkhaja/jsemver.git scm:git:ssh://git@github.com/zafarkhaja/jsemver.git UTF-8 org.junit.jupiter junit-jupiter 5.10.1 test ossrh https://oss.sonatype.org/content/repositories/snapshots/ ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/ org.apache.maven.plugins maven-compiler-plugin 3.12.1 1.8 1.8 UTF-8 -Xlint:all org.apache.maven.plugins maven-surefire-plugin 3.2.3 org.apache.maven.plugins maven-jar-plugin 3.3.0 true com.github.zafarkhaja.semver org.sonatype.plugins nexus-staging-maven-plugin 1.6.13 true ossrh https://oss.sonatype.org/ false release org.apache.maven.plugins maven-source-plugin 3.3.0 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 3.6.3 none attach-javadocs jar org.apache.maven.plugins maven-gpg-plugin 3.1.0 sign-artifacts verify sign jsemver-0.10.2/src/000077500000000000000000000000001455073306400140435ustar00rootroot00000000000000jsemver-0.10.2/src/main/000077500000000000000000000000001455073306400147675ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/000077500000000000000000000000001455073306400157105ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/000077500000000000000000000000001455073306400164665ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/000077500000000000000000000000001455073306400177505ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/zafarkhaja/000077500000000000000000000000001455073306400220525ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/000077500000000000000000000000001455073306400233535ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/ParseException.java000066400000000000000000000050761455073306400271570ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Thrown to indicate an error during the parsing. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public class ParseException extends RuntimeException { private static final long serialVersionUID = 0L; /** * Constructs a {@code ParseException} instance with no error message. */ public ParseException() { super(); } /** * Constructs a {@code ParseException} instance with an error message. * * @param message the error message */ public ParseException(String message) { super(message); } /** * Constructs a {@code ParseException} instance with an error message * and the cause exception. * * @param message the error message * @param cause an exception that caused this exception */ public ParseException(String message, UnexpectedCharacterException cause) { super(message); initCause(cause); } /** * Returns the string representation of this exception. * * @return the string representation of this exception */ @Override public String toString() { Throwable cause = getCause(); String msg = getMessage(); if (msg != null) { msg += ((cause != null) ? " (" + cause + ")" : ""); return msg; } return ((cause != null) ? cause.toString() : ""); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/Parser.java000066400000000000000000000030141455073306400254500ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * A parser interface. * * @param the type of parser's output * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public interface Parser { /** * Parses the input string. * * @param input the string to parse * @return the Abstract Syntax Tree */ T parse(String input); } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java000066400000000000000000000104661455073306400320250ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import com.github.zafarkhaja.semver.VersionParser.CharType; import com.github.zafarkhaja.semver.util.UnexpectedElementException; import java.util.Arrays; /** * Thrown when attempting to consume a character of unexpected types. *

* This exception is a wrapper exception extending {@code ParseException}. * * @author Zafar Khaja {@literal } * @since 0.8.0 */ public class UnexpectedCharacterException extends ParseException { private static final long serialVersionUID = 0L; /** * The unexpected character. */ private final Character unexpected; /** * The position of the unexpected character. */ private final int position; /** * The array of expected character types. */ private final CharType[] expected; /** * Constructs a {@code UnexpectedCharacterException} instance with * the wrapped {@code UnexpectedElementException} exception. * * @param cause the wrapped exception */ UnexpectedCharacterException(UnexpectedElementException cause) { this( (Character) cause.getUnexpectedElement(), cause.getPosition(), (CharType[]) cause.getExpectedElementTypes() ); } /** * Constructs a {@code UnexpectedCharacterException} instance * with the unexpected character, its position and the expected types. * * @param unexpected the unexpected character * @param position the position of the unexpected character * @param expected an array of the expected character types */ UnexpectedCharacterException(Character unexpected, int position, CharType... expected) { super(createMessage(unexpected, position, expected)); this.unexpected = unexpected; this.position = position; this.expected = expected; } /** * Gets the unexpected character. * * @return the unexpected character */ Character getUnexpectedCharacter() { return unexpected; } /** * Gets the position of the unexpected character. * * @return the position of the unexpected character */ int getPosition() { return position; } /** * Gets the expected character types. * * @return an array of expected character types */ CharType[] getExpectedCharTypes() { return expected; } /** * Returns the string representation of this exception * containing the information about the unexpected * element and, if available, about the expected types. * * @return the string representation of this exception */ @Override public String toString() { return getMessage(); } private static String createMessage(Character unexpected, int position, CharType... expected) { String msg = String.format( "Unexpected character %s(%s) at position %d", CharType.forCharacter(unexpected), unexpected, position ); if (expected.length > 0) { msg += String.format(", expecting %s", Arrays.toString(expected)); } return msg; } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/Version.java000066400000000000000000001770751455073306400256640ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import com.github.zafarkhaja.semver.expr.Expression; import com.github.zafarkhaja.semver.expr.ExpressionParser; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; import java.util.Locale; import java.util.Optional; import java.util.function.Predicate; import static com.github.zafarkhaja.semver.Version.Validators.*; import static com.github.zafarkhaja.semver.VersionParser.parseBuild; import static com.github.zafarkhaja.semver.VersionParser.parsePreRelease; /** * A representation of version as defined by the SemVer Specification. *

* The {@code Version} class is immutable and thread-safe. * * @author Zafar Khaja {@literal } * @since 0.1.0 */ @SuppressWarnings("serial") public class Version implements Comparable, Serializable { /** * A mutable builder for the immutable {@code Version} class */ public static class Builder { private long major = 0; private long minor = 0; private long patch = 0; private String[] preReleaseIds = {}; private String[] buildIds = {}; /** * Default constructor, initializes fields with default values (0.0.0) */ public Builder() {} /** * Sets the major version; the minor and patch versions are assigned 0. * * @param major a major version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code major} is negative * @since 0.10.0 */ public Builder setVersionCore(long major) { return setVersionCore(major, 0, 0); } /** * Sets the major and minor versions; the patch version is assigned 0. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if any of the arguments is negative * @since 0.10.0 */ public Builder setVersionCore(long major, long minor) { return setVersionCore(major, minor, 0); } /** * Sets major, minor and patch versions. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if any of the arguments is negative * @since 0.10.0 */ public Builder setVersionCore(long major, long minor, long patch) { return setMajorVersion(major). setMinorVersion(minor). setPatchVersion(patch) ; } /** * Sets the major version. * * @param major a major version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code major} is negative * @since 0.10.0 */ public Builder setMajorVersion(long major) { this.major = nonNegative(major, "major"); return this; } /** * Sets the minor version. * * @param minor a minor version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code minor} is negative * @since 0.10.0 */ public Builder setMinorVersion(long minor) { this.minor = nonNegative(minor, "minor"); return this; } /** * Sets the patch version. * * @param patch a patch version number, non-negative * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code patch} is negative * @since 0.10.0 */ public Builder setPatchVersion(long patch) { this.patch = nonNegative(patch, "patch"); return this; } /** * Sets the pre-release version. *

* Multiple identifiers can be specified in a single argument joined * with dots, or in separate arguments, or both. * * @param ids one or more pre-release identifiers, non-null * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code ids} is null/empty or contains null */ public Builder setPreReleaseVersion(String... ids) { preReleaseIds = oneOrMoreNonNulls(ids, "ids").clone(); return this; } /** * Appends (additional) pre-release identifier(s). *

* If no pre-release identifiers have been previously set, the method * works as {@link #setPreReleaseVersion(String...)}. *

* Multiple identifiers can be specified in a single argument joined * with dots, or in separate arguments, or both. * * @param ids one or more pre-release identifiers, non-null * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code ids} is null/empty or contains null * @see #setPreReleaseVersion(String...) * @since 0.10.0 */ public Builder addPreReleaseIdentifiers(String... ids) { if (preReleaseIds.length == 0) { return setPreReleaseVersion(ids); } preReleaseIds = concatArrays(preReleaseIds, oneOrMoreNonNulls(ids, "ids")); return this; } /** * Unsets the pre-release version. * * @return this {@code Builder} instance * @since 0.10.0 */ public Builder unsetPreReleaseVersion() { preReleaseIds = new String[0]; return this; } /** * Sets the build metadata. *

* Multiple identifiers can be specified in a single argument joined * with dots, or in separate arguments, or both. * * @param ids one or more build identifiers, non-null * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code ids} is null/empty or contains null */ public Builder setBuildMetadata(String... ids) { buildIds = oneOrMoreNonNulls(ids, "ids").clone(); return this; } /** * Appends (additional) build identifier(s). *

* If no build identifiers have been previously set, the method works as * {@link #setBuildMetadata(String...)}. *

* Multiple identifiers can be specified in a single argument joined * with dots, or in separate arguments, or both. * * @param ids one or more build identifiers, non-null * @return this {@code Builder} instance * @throws IllegalArgumentException if {@code ids} is null/empty or contains null * @see #setBuildMetadata(String...) * @since 0.10.0 */ public Builder addBuildIdentifiers(String... ids) { if (buildIds.length == 0) { return setBuildMetadata(ids); } buildIds = concatArrays(buildIds, oneOrMoreNonNulls(ids, "ids")); return this; } /** * Unsets the build metadata. * * @return this {@code Builder} instance * @since 0.10.0 */ public Builder unsetBuildMetadata() { buildIds = new String[0]; return this; } /** * Obtains a {@code Version} instance with previously set values. * * @return a {@code Version} instance * @throws ParseException if any of the previously set identifiers can't be parsed * @see Version#of(long, long, long, String, String) */ public Version build() { return Version.of( major, minor, patch, joinIdentifiers(preReleaseIds), joinIdentifiers(buildIds) ); } private static String[] concatArrays(String[] ids1, String[] ids2) { String[] ids = new String[ids1.length + ids2.length]; System.arraycopy(ids1, 0, ids, 0, ids1.length); System.arraycopy(ids2, 0, ids, ids1.length, ids2.length); return ids; } /** * @deprecated forRemoval since 0.10.0 * * @param normal a string representing a normal version, non-null * @throws IllegalArgumentException if (@code normal) is null */ @Deprecated public Builder(String normal) { setNormalVersion(normal); } /** * @deprecated forRemoval since 0.10.0 * * @param normal a string representing a normal version, non-null * @return this {@code Builder} instance * @throws IllegalArgumentException if (@code normal) is null */ @Deprecated @SuppressWarnings("DeprecatedIsStillUsed") public Builder setNormalVersion(String normal) { String[] parts = nonNull(normal, "normal").split("\\" + IDENTIFIER_SEPARATOR); return setVersionCore( Long.parseLong(parts[0]), parts.length > 1 ? Long.parseLong(parts[1]) : 0, parts.length > 2 ? Long.parseLong(parts[2]) : 0 ); } } /** * A comparator that sorts versions in increment order, from lowest to highest. *

* The comparator is intended for use in comparison-based data structures. * * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public static final Comparator INCREMENT_ORDER = Version::compareToIgnoreBuildMetadata; /** * A comparator that sorts versions in (highest) precedence order. *

* The ordering imposed by this comparator is reverse of the "natural" * increment ordering, that is, versions are arranged in descending order * from highest-precedence to lowest-precedence. *

* The comparator is intended for use in comparison-based data structures. * * @see #INCREMENT_ORDER * @since 0.10.0 */ public static final Comparator PRECEDENCE_ORDER = INCREMENT_ORDER.reversed(); private final long major; private final long minor; private final long patch; private final String[] preReleaseIds; private final String[] buildIds; private static final String IDENTIFIER_SEPARATOR = "."; private static final String PRE_RELEASE_PREFIX = "-"; private static final String BUILD_PREFIX = "+"; /** * @see #Version(long, long, long, String[], String[]) for documentation */ Version(long major, long minor, long patch) { this(major, minor, patch, new String[0], new String[0]); } /** * @see #Version(long, long, long, String[], String[]) for documentation */ Version(long major, long minor, long patch, String[] preReleaseIds) { this(major, minor, patch, preReleaseIds, new String[0]); } /** * Package-private constructor, for internal use only. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @param preReleaseIds the pre-release identifiers, non-null * @param buildIds the build identifiers, non-null * @throws IllegalArgumentException if any of the numeric arguments is negative, * or if any of the reference-type arguments is null */ Version(long major, long minor, long patch, String[] preReleaseIds, String[] buildIds) { this.major = nonNegative(major, "major"); this.minor = nonNegative(minor, "minor"); this.patch = nonNegative(patch, "patch"); this.preReleaseIds = nonNull(preReleaseIds, "preReleaseIds").clone(); this.buildIds = nonNull(buildIds, "buildIds").clone(); } /** * Obtains a {@code Version} instance by parsing the specified string in * strict mode, which ensures full compliance with the specification. * * @param version a string representing a SemVer version, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code version} is null * @throws ParseException if {@code version} can't be parsed * @see #parse(String, boolean) * @since 0.10.0 */ public static Version parse(String version) { return parse(version, true); } /** * Obtains a {@code Version} instance by parsing the specified string. *

* This method provides a way to parse the specified string in lenient mode, * which accepts shorter version cores, such as "1" or "1.2". * * @param version a string representing a SemVer version, non-null * @param strictly whether to parse the specified string in strict mode * @return a {@code Version} instance * @throws IllegalArgumentException if {@code version} is null * @throws ParseException if {@code version} can't be parsed * @see #parse(String) * @since 0.10.0 */ public static Version parse(String version, boolean strictly) { return VersionParser.parseValidSemVer(nonNull(version, "version"), strictly); } /** * Tries to obtain a {@code Version} instance by parsing the specified string * in strict mode, which ensures full compliance with the specification. * * @param version a string representing a SemVer version, nullable * @return an {@code Optional} with a {@code Version} instance, if the * specified string can be parsed; empty {@code Optional} otherwise * @see #tryParse(String, boolean) * @since 0.10.0 */ public static Optional tryParse(String version) { return tryParse(version, true); } /** * Tries to obtain a {@code Version} instance by parsing the specified string. *

* This method provides a way to parse the specified string in lenient mode, * which accepts shorter version cores, such as "1" or "1.2". * * @param version a string representing a SemVer version, nullable * @param strictly whether to parse the specified string in strict mode * @return an {@code Optional} with a {@code Version} instance, if the * specified string can be parsed; empty {@code Optional} otherwise * @see #tryParse(String) * @since 0.10.0 */ public static Optional tryParse(String version, boolean strictly) { try { return Optional.of(Version.parse(version, strictly)); } catch (RuntimeException e) { return Optional.empty(); } } /** * Checks validity of the specified SemVer version string in strict mode, * which ensures full compliance with the specification. *

* Note that internally this method makes use of {@link #parse(String)} and * suppresses any exceptions, so using it to avoid dealing with exceptions * like so: * *

{@code
     *   String version = "1.2.3";
     *   if (Version.isValid(version)) {
     *     Version v = Version.parse(version);
     *   }
     * }
* * would mean parsing the same version string twice. In this case, as an * alternative, consider using {@link #tryParse(String)}. * * @param version a string representing a SemVer version, nullable * @return {@code true}, if the specified string is a valid SemVer version; * {@code false} otherwise * @see #isValid(String, boolean) * @since 0.10.0 */ public static boolean isValid(String version) { return isValid(version, true); } /** * Checks validity of the specified SemVer version string. *

* This method provides a way to parse the specified string in lenient mode, * which accepts shorter version cores, such as "1" or "1.2". * * @param version a string representing a SemVer version, nullable * @param strictly whether to parse the specified string in strict mode * @return {@code true}, if the specified string is a valid SemVer version; * {@code false} otherwise * @see #isValid(String) * @since 0.10.0 */ public static boolean isValid(String version, boolean strictly) { return tryParse(version, strictly).isPresent(); } /** * Obtains a {@code Version} instance of the specified major version. * * @param major a major version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} is negative * @since 0.10.0 */ public static Version of(long major) { return Version.of(major, 0, 0, null, null); } /** * Obtains a {@code Version} instance of the specified major and pre-release * versions. * * @param major a major version number, non-negative * @param preRelease a pre-release version label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} is negative * @throws ParseException if {@code preRelease} can't be parsed * @since 0.10.0 */ public static Version of(long major, String preRelease) { return Version.of(major, 0, 0, preRelease, null); } /** * Obtains a {@code Version} instance of the specified major and pre-release * versions, as well as build metadata. * * @param major a major version number, non-negative * @param preRelease a pre-release version label, nullable * @param build a build metadata label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} is negative * @throws ParseException if {@code preRelease} or {@code build} can't be parsed * @since 0.10.0 */ public static Version of(long major, String preRelease, String build) { return Version.of(major, 0, 0, preRelease, build); } /** * Obtains a {@code Version} instance of the specified major and minor versions. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} or {@code minor} is negative * @since 0.10.0 */ public static Version of(long major, long minor) { return Version.of(major, minor, 0, null, null); } /** * Obtains a {@code Version} instance of the specified major, minor and * pre-release versions. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param preRelease a pre-release version label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} or {@code minor} is negative * @throws ParseException if {@code preRelease} can't be parsed * @since 0.10.0 */ public static Version of(long major, long minor, String preRelease) { return Version.of(major, minor, 0, preRelease, null); } /** * Obtains a {@code Version} instance of the specified major, minor and * pre-release versions, as well as build metadata. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param preRelease a pre-release version label, nullable * @param build a build metadata label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} or {@code minor} is negative * @throws ParseException if {@code preRelease} or {@code build} can't be parsed * @since 0.10.0 */ public static Version of(long major, long minor, String preRelease, String build) { return Version.of(major, minor, 0, preRelease, build); } /** * Obtains a {@code Version} instance of the specified major, minor and * patch versions. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if any of the arguments is negative * @since 0.10.0 */ public static Version of(long major, long minor, long patch) { return Version.of(major, minor, patch, null, null); } /** * Obtains a {@code Version} instance of the specified major, minor, patch * and pre-release versions. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @param preRelease a pre-release version label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if any of the numeric arguments is negative * @throws ParseException if {@code preRelease} can't be parsed * @since 0.10.0 */ public static Version of(long major, long minor, long patch, String preRelease) { return Version.of(major, minor, patch, preRelease, null); } /** * Obtains a {@code Version} instance of the specified major, minor, patch * and pre-release versions, as well as build metadata. * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @param preRelease a pre-release version label, nullable * @param build a build metadata label, nullable * @return a {@code Version} instance * @throws IllegalArgumentException if any of the numeric arguments is negative * @throws ParseException if {@code preRelease} or {@code build} can't be parsed * @since 0.10.0 */ public static Version of(long major, long minor, long patch, String preRelease, String build) { return new Version( major, minor, patch, preRelease == null ? new String[0] : parsePreRelease(preRelease), build == null ? new String[0] : parseBuild(build) ); } /** * Returns this {@code Version}'s major version. * * @return the major version number * @since 0.10.0 */ public long majorVersion() { return major; } /** * Returns this {@code Version}'s minor version. * * @return the minor version number * @since 0.10.0 */ public long minorVersion() { return minor; } /** * Returns this {@code Version}'s patch version. * * @return the patch version number * @since 0.10.0 */ public long patchVersion() { return patch; } /** * Returns this {@code Version}'s pre-release version in the form of * dot-separated identifiers. * * @return the pre-release version label, if present * @since 0.10.0 */ public Optional preReleaseVersion() { return Optional.ofNullable(joinIdentifiers(preReleaseIds)); } /** * Returns this {@code Version}'s build metadata in the form of * dot-separated identifiers. * * @return the build metadata label, if present * @since 0.10.0 */ public Optional buildMetadata() { return Optional.ofNullable(joinIdentifiers(buildIds)); } /** * Obtains the next {@code Version} by incrementing the major version number * by one, with an optional pre-release version label. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws ArithmeticException if the major version number overflows * @throws IllegalArgumentException if {@code preReleaseIds} is null or contains null * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextMajorVersion(String... preReleaseIds) { return nextMajorVersion(safeIncrement(major), preReleaseIds); } /** * Obtains the next {@code Version} of the specified major version number, * with an optional pre-release version label. *

* The specified major version number must be higher than this {@code Version}'s * major version. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param major the next major version number, non-negative * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} is negative, or if * {@code preReleaseIds} is null or contains null * @throws IllegalStateException if {@code major} is lower than or equivalent * to this {@code Version}'s major version * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextMajorVersion(long major, String... preReleaseIds) { if (this.major >= nonNegative(major, "major")) { throw new IllegalStateException("This major version is higher or equivalent"); } String preRelease = joinIdentifiers(zeroOrMoreNonNulls(preReleaseIds, "preReleaseIds")); return Version.of(major, 0, 0, preRelease); } /** * Obtains the next {@code Version} by incrementing the minor version number * by one, with an optional pre-release version label. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws ArithmeticException if the minor version number overflows * @throws IllegalArgumentException if {@code preReleaseIds} is null or contains null * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextMinorVersion(String... preReleaseIds) { return nextMinorVersion(safeIncrement(minor), preReleaseIds); } /** * Obtains the next {@code Version} of the specified minor version number, * with an optional pre-release version label. *

* The specified minor version number must be higher than this {@code Version}'s * minor version. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param minor the next minor version number, non-negative * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code minor} is negative, or if * {@code preReleaseIds} is null or contains null * @throws IllegalStateException if {@code minor} is lower than or equivalent * to this {@code Version}'s minor version * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextMinorVersion(long minor, String... preReleaseIds) { if (this.minor >= nonNegative(minor, "minor")) { throw new IllegalStateException("This minor version is higher or equivalent"); } String preRelease = joinIdentifiers(zeroOrMoreNonNulls(preReleaseIds, "preReleaseIds")); return Version.of(major, minor, 0, preRelease); } /** * Obtains the next {@code Version} by incrementing the patch version number * by one, with an optional pre-release version label. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws ArithmeticException if the patch version number overflows * @throws IllegalArgumentException if {@code preReleaseIds} is null or contains null * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextPatchVersion(String... preReleaseIds) { return nextPatchVersion(safeIncrement(patch), preReleaseIds); } /** * Obtains the next {@code Version} of the specified patch version number, * with an optional pre-release version label. *

* The specified patch version number must be higher than this {@code Version}'s * patch version. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param patch the next patch version number, non-negative * @param preReleaseIds zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code patch} is negative, or if * {@code preReleaseIds} is null or contains null * @throws IllegalStateException if {@code patch} is lower than or equivalent * to this {@code Version}'s patch version * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextPatchVersion(long patch, String... preReleaseIds) { if (this.patch >= nonNegative(patch, "patch")) { throw new IllegalStateException("This patch version is higher or equivalent"); } String preRelease = joinIdentifiers(zeroOrMoreNonNulls(preReleaseIds, "preReleaseIds")); return Version.of(major, minor, patch, preRelease); } /** * Obtains the next {@code Version} by incrementing or replacing the * pre-release version. *

* If no pre-release identifiers are specified, the current pre-release * version's last numeric identifier is incremented. If the current * pre-release version's last identifier is not numeric, a new numeric * identifier of value "0" is appended for this operation. If specified, * however, the pre-release identifiers replace the current pre-release * version. The new pre-release version must be higher than this * {@code Version}'s pre-release version. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. *

* This method drops the build metadata, if present. * * @param ids zero or more pre-release identifiers, non-null * @return a {@code Version} instance * @throws ArithmeticException if the incremented numeric identifier overflows * @throws IllegalArgumentException if {@code ids} is null or contains null * @throws IllegalStateException if invoked on a stable {@code Version}, or * if the specified pre-release version is lower than or equivalent * to this {@code Version}'s pre-release version * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version nextPreReleaseVersion(String... ids) { if (!isPreRelease()) { throw new IllegalStateException("Not a pre-release version"); } zeroOrMoreNonNulls(ids, "ids"); String[] newPreReleaseIds; if (ids.length > 0) { newPreReleaseIds = parsePreRelease(joinIdentifiers(ids)); if (compareIdentifierArrays(preReleaseIds, newPreReleaseIds) >= 0) { throw new IllegalStateException("This pre-release version is higher or equivalent"); } } else { newPreReleaseIds = incrementIdentifiers(preReleaseIds); } return new Version(major, minor, patch, newPreReleaseIds); } /** * Obtains the next {@code Version} by dropping the pre-release version. *

* This method drops the build metadata, if present. * * @return a {@code Version} instance * @since 0.10.0 */ public Version toStableVersion() { return isStable() ? this : new Version(major, minor, patch); } /** * Obtains a new {@code Version} with the specified build identifiers. *

* Multiple identifiers can be specified in a single argument joined with * dots, or in separate arguments, or both. * * @param ids one or more build identifiers, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code ids} is null/empty or contains null * @throws ParseException if any of the specified identifiers can't be parsed * @since 0.10.0 */ public Version withBuildMetadata(String... ids) { String[] newBuildIds = parseBuild(joinIdentifiers(oneOrMoreNonNulls(ids, "ids"))); return new Version(major, minor, patch, preReleaseIds, newBuildIds); } /** * Obtains a (new) {@code Version} without build metadata. * * @return a {@code Version} instance * @since 0.10.0 */ public Version withoutBuildMetadata() { return !buildMetadata().isPresent() ? this : new Version(major, minor, patch, preReleaseIds); } /** * Checks if this {@code Version} satisfies the specified predicate. * * @param predicate a predicate to test, non-null * @return {@code true}, if this {@code Version} satisfies the predicate; * {@code false} otherwise * @throws IllegalArgumentException if {@code predicate} is null * @since 0.10.0 */ public boolean satisfies(Predicate predicate) { return nonNull(predicate, "predicate").test(this); } /** * Checks if this {@code Version} satisfies the specified range expression. * * @param expr a SemVer Expression string, non-null * @return {@code true}, if this {@code Version} satisfies the specified * expression; {@code false} otherwise * @throws IllegalArgumentException if {@code expr} is null * @throws ParseException if {@code expr} can't be parsed * @since 0.7.0 */ public boolean satisfies(String expr) { Parser parser = ExpressionParser.newInstance(); return satisfies(parser.parse(nonNull(expr, "expr"))); } /** * Checks if this {@code Version} represents a pre-release version. *

* This method is opposite of {@link #isStable()}. * * @return {@code true}, if this {@code Version} represents a pre-release * version; {@code false} otherwise * @see #isStable() * @since 0.10.0 */ public boolean isPreRelease() { return preReleaseVersion().isPresent(); } /** * Checks if this {@code Version} represents a stable version. *

* Pre-release versions are considered unstable. (SemVer p.9) * * @return {@code true}, if this {@code Version} represents a stable * version; {@code false} otherwise * @see #isPreRelease() * @since 0.10.0 */ public boolean isStable() { return !isPreRelease(); } /** * Checks if this {@code Version} represents a stable public API. *

* Versions lower than 1.0.0 are for initial development, therefore the * public API should not be considered stable. (SemVer p.4) * * @return {@code true}, if this {@code Version} represents a stable public * API; {@code false} otherwise * @since 0.10.0 */ public boolean isPublicApiStable() { return isHigherThanOrEquivalentTo(Version.of(1)); } /** * Checks if this {@code Version} is compatible with the specified {@code Version} * in terms of their public API. *

* Two versions are compatible in terms of public API iff they have the * same major version of 1 or higher. Being public API compatible doesn't * necessarily mean both versions have the same set of public API units. * It only means that the versions are interchangeable. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if the versions are compatible in terms of public API; * {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @since 0.10.0 */ public boolean isPublicApiCompatibleWith(Version other) { return isPublicApiStable() && isSameMajorVersionAs(other); } /** * Checks if this {@code Version} is compatible with the specified {@code Version} * in terms of their major versions. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if both versions have the same major version; * {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @since 0.10.0 */ public boolean isSameMajorVersionAs(Version other) { nonNull(other, "other"); return major == other.major; } /** * Checks if this {@code Version} is compatible with the specified {@code Version} * in terms of their major and minor versions. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if both versions have the same major and minor versions; * {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @since 0.10.0 */ public boolean isSameMinorVersionAs(Version other) { nonNull(other, "other"); return major == other.major && minor == other.minor; } /** * Checks if this {@code Version} is compatible with the specified {@code Version} * in terms of their major, minor and patch versions. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if both versions have the same major, minor and patch * versions; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @since 0.10.0 */ public boolean isSamePatchVersionAs(Version other) { nonNull(other, "other"); return major == other.major && minor == other.minor && patch == other.patch; } /** * Determines if this {@code Version} has a higher precedence compared with * the specified {@code Version}. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is higher than the other * {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public boolean isHigherThan(Version other) { return compareToIgnoreBuildMetadata(other) > 0; } /** * Determines if this {@code Version} has a higher or equal precedence * compared with the specified {@code Version}. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is higher than or equivalent * to the other {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public boolean isHigherThanOrEquivalentTo(Version other) { return compareToIgnoreBuildMetadata(other) >= 0; } /** * Determines if this {@code Version} has a lower precedence compared with * the specified {@code Version}. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is lower than the other * {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public boolean isLowerThan(Version other) { return compareToIgnoreBuildMetadata(other) < 0; } /** * Determines if this {@code Version} has a lower or equal precedence * compared with the specified {@code Version}. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is lower than or equivalent * to the other {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public boolean isLowerThanOrEquivalentTo(Version other) { return compareToIgnoreBuildMetadata(other) <= 0; } /** * Determines if this {@code Version} has the same precedence as the * specified {@code Version}. *

* As per SemVer p.10, build metadata is ignored when determining version * precedence. To test for exact equality, including build metadata, use * {@link #equals(Object)}. * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is equivalent to the other * {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null * @see #compareToIgnoreBuildMetadata(Version) * @since 0.10.0 */ public boolean isEquivalentTo(Version other) { return compareToIgnoreBuildMetadata(other) == 0; } /** * Compares versions, along with their build metadata. *

* Note that this method violates the SemVer p.10 ("build metadata must be * ignored") rule, hence can't be used for determining version precedence. * It was made so intentionally for it to be consistent with {@code equals} * as defined by {@link Comparable}, and to be used in comparison-based data * structures. *

* As the Specification defines no comparison rules for build metadata, this * behavior is strictly implementation-defined. Build metadata are compared * similarly to pre-release versions. A version with build metadata is * ordered after an equivalent one without it. *

* To compare Versions without their build metadata in order to determine * precedence use {@link #compareToIgnoreBuildMetadata(Version)}. * * @param other the {@code Version} to compare with, non-null * @return a negative integer, zero or a positive integer if this * {@code Version} is less than, equal to or greater than the * specified {@code Version} * @throws IllegalArgumentException if {@code other} is null */ @Override public int compareTo(Version other) { int result = compareToIgnoreBuildMetadata(other); if (result != 0) { return result; } result = compareIdentifierArrays(this.buildIds, other.buildIds); if (this.buildIds.length == 0 || other.buildIds.length == 0) { result = -1 * result; } return result; } /** * Compares versions, ignoring their build metadata. *

* This method adheres to the comparison rules defined by the Specification, * and as such can be used for determining version precedence, either as a * natural-order comparator ({@code Version::compareToIgnoreBuildMetadata}), * or as a regular method. * * @param other the {@code Version} to compare with, non-null * @return a negative integer, zero or a positive integer if this * {@code Version} is lower than, equivalent to or higher than the * specified {@code Version} * @throws IllegalArgumentException if {@code other} is null * @since 0.10.0 */ public int compareToIgnoreBuildMetadata(Version other) { nonNull(other, "other"); long result = major - other.major; if (result == 0) { result = minor - other.minor; if (result == 0) { result = patch - other.patch; if (result == 0) { return compareIdentifierArrays(this.preReleaseIds, other.preReleaseIds); } } } return result < 0 ? -1 : 1; } /** * Checks if this {@code Version} exactly equals the specified {@code Version}. *

* Although primarily intended for use in hash-based data structures, it * can be used for testing for exact equality, including build metadata, if * needed. To test for equivalence use {@link #isEquivalentTo(Version)}. * * @param other the {@code Version} to compare with, nullable * @return {@code true}, if this {@code Version} exactly equals the other * {@code Version}; {@code false} otherwise */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof Version)) { return false; } return compareTo((Version) other) == 0; } /** * {@inheritDoc} */ @Override public int hashCode() { int hash = 5; hash = 97 * hash + Long.hashCode(major); hash = 97 * hash + Long.hashCode(minor); hash = 97 * hash + Long.hashCode(patch); hash = 97 * hash + Arrays.hashCode(preReleaseIds); hash = 97 * hash + Arrays.hashCode(buildIds); return hash; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(major); sb.append(IDENTIFIER_SEPARATOR); sb.append(minor); sb.append(IDENTIFIER_SEPARATOR); sb.append(patch); preReleaseVersion().ifPresent(r -> sb.append(PRE_RELEASE_PREFIX).append(r)); buildMetadata().ifPresent(b -> sb.append(BUILD_PREFIX).append(b)); return sb.toString(); } /** * Converts this {@code Version} to {@code Builder}. *

* This method allows to use an instance of {@code Version} as a template * for new instances. * * @return a {@code Builder} instance populated with values from * this {@code Version} * @since 0.10.0 */ public Builder toBuilder() { Builder b = new Builder(); b.setVersionCore(major, minor, patch); preReleaseVersion().ifPresent(b::setPreReleaseVersion); buildMetadata().ifPresent(b::setBuildMetadata); return b; } private static long safeIncrement(long l) { return Math.incrementExact(l); } private static String joinIdentifiers(String... ids) { return ids.length == 0 ? null : String.join(IDENTIFIER_SEPARATOR, ids); } private static String[] incrementIdentifiers(String[] ids) { String[] newIds; String lastId = ids[ids.length - 1]; if (isNumeric(lastId)) { newIds = Arrays.copyOf(ids, ids.length); newIds[newIds.length - 1] = String.valueOf(safeIncrement(Long.parseLong(lastId))); } else { newIds = Arrays.copyOf(ids, ids.length + 1); newIds[newIds.length - 1] = String.valueOf(1); } return newIds; } private static int compareIdentifierArrays(String[] thisIds, String[] otherIds) { if (thisIds.length == 0 && otherIds.length == 0) { return 0; } if (thisIds.length == 0 || otherIds.length == 0) { // Pre-release versions have a lower precedence than // the associated normal version. (SemVer p.9) return thisIds.length == 0 ? 1 : -1; } int result = 0; int minLength = Math.min(thisIds.length, otherIds.length); for (int i = 0; i < minLength; i++) { result = compareIdentifiers(thisIds[i], otherIds[i]); if (result != 0) { break; } } if (result == 0) { // A larger set of pre-release fields has a higher // precedence than a smaller set, if all of the // preceding identifiers are equal. (SemVer p.11) result = thisIds.length - otherIds.length; } return result; } private static int compareIdentifiers(String thisId, String otherId) { if (isNumeric(thisId) && isNumeric(otherId)) { return Long.valueOf(thisId).compareTo(Long.valueOf(otherId)); } else { return thisId.compareTo(otherId); } } private static boolean isNumeric(String id) { // filters out if (id.startsWith("0")) { return false; } return id.chars().allMatch(Character::isDigit); } static class Validators { static long nonNegative(long arg, String name) { if (arg < 0) { throw new IllegalArgumentException(name + " must not be negative"); } return arg; } static T nonNull(T arg, String name) { return nonNullOrThrow(arg, name + " must not be null"); } static T[] nonEmpty(T[] arg, String name) { if (nonNull(arg, name).length == 0) { throw new IllegalArgumentException(name + " must not be empty"); } return arg; } static T[] zeroOrMoreNonNulls(T[] arg, String name) { for (T t : nonNull(arg, name)) { nonNullOrThrow(t, name + " must not contain null"); } return arg; } static T[] oneOrMoreNonNulls(T[] arg, String name) { for (T t : nonEmpty(arg, name)) { nonNullOrThrow(t, name + " must not contain null"); } return arg; } private static T nonNullOrThrow(T arg, String msg) { if (arg == null) { throw new IllegalArgumentException(msg); } return arg; } } private static class SerializationProxy implements Serializable { private static final long serialVersionUID = 0L; /** * @serial string representation of valid SemVer version, the most * stable logical form of the {@code Version} class, which doesn't * depend on its internal implementation. Only Specification can * affect it by redefining its semantics and hence changing the way * it's parsed. The only downside of this form is that it requires * parsing on deserialization, which shouldn't be that big of a * problem considering the size of a typical version string. */ private final String version; SerializationProxy(Version version) { this.version = version.toString(); } private Object readResolve() { return Version.parse(version); } } private Object writeReplace() { return new SerializationProxy(this); } private void readObject(ObjectInputStream ois) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } /** * @deprecated forRemoval since 0.10.0, use {@link #compareTo(Version)} */ @Deprecated public static final Comparator BUILD_AWARE_ORDER = Version::compareTo; /** * @deprecated forRemoval since 0.10.0, use {@link #parse(String)} * * @param version a string representing a SemVer version, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code version} is null * @throws ParseException if {@code version} can't be parsed */ @Deprecated public static Version valueOf(String version) { return Version.parse(version); } /** * @deprecated forRemoval since 0.10.0, use {@link #of(long)} * * @param major a major version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} is negative */ @Deprecated public static Version forIntegers(int major) { return Version.of(major); } /** * @deprecated forRemoval since 0.10.0, use {@link #of(long, long)} * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if {@code major} or {@code minor} is negative */ @Deprecated public static Version forIntegers(int major, int minor) { return Version.of(major, minor); } /** * @deprecated forRemoval since 0.10.0, use {@link #of(long, long, long)} * * @param major a major version number, non-negative * @param minor a minor version number, non-negative * @param patch a patch version number, non-negative * @return a {@code Version} instance * @throws IllegalArgumentException if any of the arguments is negative */ @Deprecated public static Version forIntegers(int major, int minor, int patch) { return Version.of(major, minor, patch); } /** * @deprecated forRemoval since 0.10.0 * * @return the version core of this {@code Version} */ @Deprecated public String getNormalVersion() { return String.format(Locale.ROOT, "%d.%d.%d", major, minor, patch); } /** * @deprecated forRemoval since 0.10.0, use {@link #majorVersion()} * * @return the major version number */ @Deprecated public int getMajorVersion() { long major = majorVersion(); if (major > Integer.MAX_VALUE) { throw new RuntimeException("major > Integer.MAX_VALUE"); } return (int) major; } /** * @deprecated forRemoval since 0.10.0, use {@link #minorVersion()} * * @return the minor version number */ @Deprecated public int getMinorVersion() { long minor = minorVersion(); if (minor > Integer.MAX_VALUE) { throw new RuntimeException("minor > Integer.MAX_VALUE"); } return (int) minor; } /** * @deprecated forRemoval since 0.10.0, use {@link #patchVersion()} * * @return the patch version number */ @Deprecated public int getPatchVersion() { long patch = patchVersion(); if (patch > Integer.MAX_VALUE) { throw new RuntimeException("patch > Integer.MAX_VALUE"); } return (int) patch; } /** * @deprecated forRemoval since 0.10.0, use {@link #preReleaseVersion()} * * @return the pre-release version label, if present; empty string otherwise */ @Deprecated public String getPreReleaseVersion() { return preReleaseVersion().orElse(""); } /** * @deprecated forRemoval since 0.10.0, use {@link #buildMetadata()} * * @return the build metadata label, if present; empty string otherwise */ @Deprecated public String getBuildMetadata() { return buildMetadata().orElse(""); } /** * @deprecated forRemoval since 0.10.0, consider using {@link #nextPreReleaseVersion(String...)} * * @param preRelease the pre-release version label, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code preRelease} is null * @throws ParseException if {@code preRelease} can't be parsed */ @Deprecated @SuppressWarnings("DeprecatedIsStillUsed") public Version setPreReleaseVersion(String preRelease) { return new Version(major, minor, patch, parsePreRelease(preRelease)); } /** * @deprecated forRemoval since 0.10.0, use {@link #withBuildMetadata(String...)} * * @param build the build metadata label, non-null * @return a {@code Version} instance * @throws IllegalArgumentException if {@code build} is null * @throws ParseException if {@code build} can't be parsed */ @Deprecated public Version setBuildMetadata(String build) { return withBuildMetadata(build); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextMajorVersion(String...)} * * @return a {@code Version} instance * @throws ArithmeticException if the major version number overflows */ @Deprecated public Version incrementMajorVersion() { return nextMajorVersion(); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextMajorVersion(String...)} * * @param preRelease the pre-release version label, non-null * @return a {@code Version} instance * @throws ArithmeticException if the major version number overflows * @throws IllegalArgumentException if {@code preRelease} is null * @throws ParseException if {@code preRelease} can't be parsed */ @Deprecated public Version incrementMajorVersion(String preRelease) { return nextMajorVersion(preRelease); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextMinorVersion(String...)} * * @return a {@code Version} instance * @throws ArithmeticException if the minor version number overflows */ @Deprecated public Version incrementMinorVersion() { return nextMinorVersion(); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextMinorVersion(String...)} * * @param preRelease the pre-release version label, non-null * @return a {@code Version} instance * @throws ArithmeticException if the minor version number overflows * @throws IllegalArgumentException if {@code preRelease} is null * @throws ParseException if {@code preRelease} can't be parsed */ @Deprecated public Version incrementMinorVersion(String preRelease) { return nextMinorVersion(preRelease); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextPatchVersion(String...)} * * @return a {@code Version} instance * @throws ArithmeticException if the patch version number overflows */ @Deprecated public Version incrementPatchVersion() { return nextPatchVersion(); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextPatchVersion(String...)} * * @param preRelease the pre-release version label, non-null * @return a {@code Version} instance * @throws ArithmeticException if the patch version number overflows * @throws IllegalArgumentException if {@code preRelease} is null * @throws ParseException if {@code preRelease} can't be parsed */ @Deprecated public Version incrementPatchVersion(String preRelease) { return nextPatchVersion(preRelease); } /** * @deprecated forRemoval since 0.10.0, use {@link #nextPreReleaseVersion(String...)} * * @return a {@code Version} instance * @throws ArithmeticException if the incremented numeric identifier overflows * @throws IllegalStateException if invoked on a stable {@code Version} */ @Deprecated public Version incrementPreReleaseVersion() { return nextPreReleaseVersion(); } /** * @deprecated forRemoval since 0.10.0 * * @return a {@code Version} instance * @throws IllegalStateException if this {@code Version} doesn't have build metadata */ @Deprecated @SuppressWarnings("DeprecatedIsStillUsed") public Version incrementBuildMetadata() { if (!buildMetadata().isPresent()) { throw new IllegalStateException("Build metadata empty"); } return new Version(major, minor, patch, preReleaseIds, incrementIdentifiers(buildIds)); } /** * @deprecated forRemoval since 0.10.0, use {@link #isHigherThan(Version)} * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is higher than the other * {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null */ @Deprecated public boolean greaterThan(Version other) { return isHigherThan(other); } /** * @deprecated forRemoval since 0.10.0, use {@link #isHigherThanOrEquivalentTo(Version)} * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is higher than or equivalent * to the other {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null */ @Deprecated public boolean greaterThanOrEqualTo(Version other) { return isHigherThanOrEquivalentTo(other); } /** * @deprecated forRemoval since 0.10.0, use {@link #isLowerThan(Version)} * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is lower than the other * {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null */ @Deprecated public boolean lessThan(Version other) { return isLowerThan(other); } /** * @deprecated forRemoval since 0.10.0, use {@link #isLowerThanOrEquivalentTo(Version)} * * @param other the {@code Version} to compare with, non-null * @return {@code true}, if this {@code Version} is lower than or equivalent * to the other {@code Version}; {@code false} otherwise * @throws IllegalArgumentException if {@code other} is null */ @Deprecated public boolean lessThanOrEqualTo(Version other) { return isLowerThanOrEquivalentTo(other); } /** * @deprecated forRemoval since 0.10.0, use {@link #compareTo(Version)} * * @param other the {@code Version} to compare with, non-null * @return a negative integer, zero or a positive integer if this * {@code Version} is less than, equal to or greater than the * specified {@code Version} * @throws IllegalArgumentException if {@code other} is null */ @Deprecated public int compareWithBuildsTo(Version other) { return compareTo(other); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/VersionParser.java000066400000000000000000000437401455073306400270300ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import com.github.zafarkhaja.semver.util.Stream; import com.github.zafarkhaja.semver.util.UnexpectedElementException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import static com.github.zafarkhaja.semver.VersionParser.CharType.*; /** * A parser for the SemVer Version. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class VersionParser implements Parser { /** * Valid character types. */ enum CharType implements Stream.ElementType { DIGIT { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { if (chr == null) { return false; } return chr >= '0' && chr <= '9'; } }, LETTER { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { if (chr == null) { return false; } return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z'); } }, DOT { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { if (chr == null) { return false; } return chr == '.'; } }, HYPHEN { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { if (chr == null) { return false; } return chr == '-'; } }, PLUS { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { if (chr == null) { return false; } return chr == '+'; } }, EOI { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { return chr == null; } }, ILLEGAL { /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Character chr) { EnumSet itself = EnumSet.of(ILLEGAL); for (CharType type : EnumSet.complementOf(itself)) { if (type.isMatchedBy(chr)) { return false; } } return true; } }; /** * Gets the type for a given character. * * @param chr the character to get the type for * @return the type of the specified character */ static CharType forCharacter(Character chr) { for (CharType type : values()) { if (type.isMatchedBy(chr)) { return type; } } return null; } } /** * Indicates if the parser is in strict mode. */ private final boolean isStrictModeOn; /** * The stream of characters. */ private final Stream chars; /** * Constructs a {@code VersionParser} instance * with the input string to parse. * * @param input the input string to parse * @throws IllegalArgumentException if the input string is {@code NULL} or empty */ VersionParser(String input) { this(input, true); } /** * Constructs a {@code VersionParser} instance with an input string to parse. * * @param input an input string to parse * @param strictModeOn a switch to turn the strict parsing mode on and off */ VersionParser(String input, boolean strictModeOn) { isStrictModeOn = strictModeOn; if (input == null || input.isEmpty()) { throw new IllegalArgumentException("Input string is NULL or empty"); } Character[] elements = new Character[input.length()]; for (int i = 0; i < input.length(); i++) { elements[i] = input.charAt(i); } chars = new Stream<>(elements); } /** * Parses the input string. * * @param input the input string to parse * @return a valid version object * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ @Override public Version parse(String input) { return parseValidSemVer(); } /** * Parses the whole version including pre-release version and build metadata. * * @param version the version string to parse * @return a valid version object * @throws IllegalArgumentException if the input string is {@code NULL} or empty * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ static Version parseValidSemVer(String version) { return parseValidSemVer(version, true); } /** * Parses the whole version including pre-release version and build metadata. * * @param version a version string to parse * @param strictModeOn a switch to turn the strict parsing mode on and off * @return a valid version instance * @throws IllegalArgumentException if the input string is {@code NULL} or empty * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ static Version parseValidSemVer(String version, boolean strictModeOn) { VersionParser parser = new VersionParser(version, strictModeOn); return parser.parseValidSemVer(); } /** * Parses the version core. * * @param versionCore the version core string to parse * @return a valid version object * @throws IllegalArgumentException if the input string is {@code NULL} or empty * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ static Version parseVersionCore(String versionCore) { VersionParser parser = new VersionParser(versionCore); long[] versionParts = parser.parseVersionCore(); return Version.of(versionParts[0], versionParts[1], versionParts[2]); } /** * Parses the pre-release version. * * @param preRelease the pre-release version string to parse * @return an array of pre-release identifiers * @throws IllegalArgumentException if the input string is {@code NULL} or empty * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ static String[] parsePreRelease(String preRelease) { VersionParser parser = new VersionParser(preRelease); String[] result = parser.parsePreRelease(); parser.ensureValidLookahead(EOI); return result; } /** * Parses the build metadata. * * @param build the build metadata string to parse * @return an array of build identifiers * @throws IllegalArgumentException if the input string is {@code NULL} or empty * @throws ParseException when there is a grammar error * @throws UnexpectedCharacterException when encounters an unexpected character type */ static String[] parseBuild(String build) { VersionParser parser = new VersionParser(build); String[] result = parser.parseBuild(); parser.ensureValidLookahead(EOI); return result; } /** * Parses the {@literal } non-terminal. * *

     * {@literal
     *  ::= 
     *                  |  "-" 
     *                  |  "+" 
     *                  |  "-"  "+" 
     * }
     * 
* * @return a valid version object */ private Version parseValidSemVer() { long[] versionParts = parseVersionCore(); String[] preRelease = new String[0]; String[] build = new String[0]; Character next = consumeNextCharacter(HYPHEN, PLUS, EOI); if (HYPHEN.isMatchedBy(next)) { preRelease = parsePreRelease(); next = consumeNextCharacter(PLUS, EOI); } if (PLUS.isMatchedBy(next)) { build = parseBuild(); } consumeNextCharacter(EOI); return new Version(versionParts[0], versionParts[1], versionParts[2], preRelease, build); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::=  "."  "." 
     * }
     * 
* * @return an array of the version core parts */ private long[] parseVersionCore() { long major = numericIdentifier(); long minor = 0; if (isStrictModeOn || chars.positiveLookahead(DOT)) { consumeNextCharacter(DOT); minor = numericIdentifier(); } long patch = 0; if (isStrictModeOn || chars.positiveLookahead(DOT)) { consumeNextCharacter(DOT); patch = numericIdentifier(); } return new long[] {major, minor, patch}; } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *
     *  ::= 
     *    |  "." 
     * }
     * 
* * @return an array of pre-release identifiers */ private String[] parsePreRelease() { ensureValidLookahead(DIGIT, LETTER, HYPHEN); List idents = new ArrayList<>(); do { idents.add(preReleaseIdentifier()); if (chars.positiveLookahead(DOT)) { consumeNextCharacter(DOT); continue; } break; } while (true); return idents.toArray(new String[0]); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *                            | 
     * }
     * 
* * @return a single pre-release identifier */ private String preReleaseIdentifier() { checkForEmptyIdentifier(); CharType boundary = nearestCharType(DOT, PLUS, EOI); if (chars.positiveLookaheadBefore(boundary, LETTER, HYPHEN)) { return alphanumericIdentifier(); } else { return String.valueOf(numericIdentifier()); } } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *
     *  ::= 
     *                |  "." 
     * }
     * 
* * @return an array of build identifiers */ private String[] parseBuild() { ensureValidLookahead(DIGIT, LETTER, HYPHEN); List idents = new ArrayList<>(); do { idents.add(buildIdentifier()); if (chars.positiveLookahead(DOT)) { consumeNextCharacter(DOT); continue; } break; } while (true); return idents.toArray(new String[0]); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *                      | 
     * }
     * 
* * @return a single build identifier */ private String buildIdentifier() { checkForEmptyIdentifier(); CharType boundary = nearestCharType(DOT, EOI); if (chars.positiveLookaheadBefore(boundary, LETTER, HYPHEN)) { return alphanumericIdentifier(); } else { return digits(); } } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= "0"
     *                        | 
     *                        |  
     * }
     * 
* * @return a string representing the numeric identifier */ private long numericIdentifier() { checkForLeadingZeroes(); try { return Long.parseLong(digits()); } catch (NumberFormatException e) { throw new ParseException("Numeric identifier overflow"); } } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *             |  
     *             |  
     *             |   
     * }
     * 
* * @return a string representing the alphanumeric identifier */ private String alphanumericIdentifier() { StringBuilder sb = new StringBuilder(); do { sb.append(consumeNextCharacter(DIGIT, LETTER, HYPHEN)); } while (chars.positiveLookahead(DIGIT, LETTER, HYPHEN)); return sb.toString(); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *            |  
     * }
     * 
* * @return a string representing the digits */ private String digits() { StringBuilder sb = new StringBuilder(); do { sb.append(consumeNextCharacter(DIGIT)); } while (chars.positiveLookahead(DIGIT)); return sb.toString(); } /** * Finds the nearest character type. * * @param types the character types to choose from * @return the nearest character type or {@code EOI} */ private CharType nearestCharType(CharType... types) { for (Character chr : chars) { for (CharType type : types) { if (type.isMatchedBy(chr)) { return type; } } } return EOI; } /** * Checks for leading zeroes in the numeric identifiers. * * @throws ParseException if a numeric identifier has leading zero(es) */ private void checkForLeadingZeroes() { Character la1 = chars.lookahead(1); Character la2 = chars.lookahead(2); if (la1 != null && la1 == '0' && DIGIT.isMatchedBy(la2)) { throw new ParseException( "Numeric identifier MUST NOT contain leading zeroes" ); } } /** * Checks for empty identifiers in the pre-release version or build metadata. * * @throws ParseException if the pre-release version or build * metadata have empty identifier(s) */ private void checkForEmptyIdentifier() { Character la = chars.lookahead(1); if (DOT.isMatchedBy(la) || PLUS.isMatchedBy(la) || EOI.isMatchedBy(la)) { throw new ParseException( "Identifiers MUST NOT be empty", new UnexpectedCharacterException( la, chars.currentOffset(), DIGIT, LETTER, HYPHEN ) ); } } /** * Tries to consume the next character in the stream. * * @param expected the expected types of the next character * @return the next character in the stream * @throws UnexpectedCharacterException when encounters an unexpected character type */ private Character consumeNextCharacter(CharType... expected) { try { return chars.consume(expected); } catch (UnexpectedElementException e) { throw new UnexpectedCharacterException(e); } } /** * Checks if the next character in the stream is valid. * * @param expected the expected types of the next character * @throws UnexpectedCharacterException if the next character is not valid */ private void ensureValidLookahead(CharType... expected) { if (!chars.positiveLookahead(expected)) { throw new UnexpectedCharacterException( chars.lookahead(1), chars.currentOffset(), expected ); } } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/000077500000000000000000000000001455073306400243315ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/And.java000066400000000000000000000044341455073306400257030ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the logical "and" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class And implements Expression { /** * The left-hand operand of expression. */ private final Expression left; /** * The right-hand operand of expression. */ private final Expression right; /** * Constructs a {@code And} expression with * the left-hand and right-hand operands. * * @param left the left-hand operand of expression * @param right the right-hand operand of expression */ And(Expression left, Expression right) { this.left = left; this.right = right; } /** * Checks if both operands evaluate to {@code true}. * * @param version the version to interpret against * @return {@code true} if both operands evaluate to {@code true} * or {@code false} otherwise */ @Override public boolean interpret(Version version) { return left.interpret(version) && right.interpret(version); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/CompositeExpression.java000066400000000000000000000237261455073306400312300ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.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 {@literal } * @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.parse(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.parse(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.parse(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.parse(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.parse(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.parse(version)); } } /** * The underlying expression tree. */ private Expression exprTree; /** * Constructs a {@code CompositeExpression} * with an underlying {@code Expression}. * * @param expr the underlying expression */ public CompositeExpression(Expression expr) { exprTree = 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) { exprTree = new And(exprTree, 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) { exprTree = new Or(exprTree, 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.parse(version)); } /** * {@inheritDoc} */ @Override public boolean interpret(Version version) { return exprTree.interpret(version); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Equal.java000066400000000000000000000042571455073306400262530ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "equal" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Equal implements Expression { /** * The parsed version, the right-hand operand of the "equal" operator. */ private final Version parsedVersion; /** * Constructs a {@code Equal} expression with the parsed version. * * @param parsedVersion the parsed version */ Equal(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version equals the parsed version. * * @param version the version to compare to, the left-hand * operand of the "equal" operator * @return {@code true} if the version equals the * parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return version.isEquivalentTo(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Expression.java000066400000000000000000000035331455073306400273370ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 java.util.function.Predicate; /** * The {@code Expression} interface is to be implemented * by the nodes of the Abstract Syntax Tree produced by * the {@code ExpressionParser} class. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public interface Expression extends Predicate { /** * Interprets the expression. * * @param version the version to interpret against * @return the result of the expression interpretation */ boolean interpret(Version version); default boolean test(Version version) { return interpret(version); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/ExpressionParser.java000066400000000000000000000357661455073306400305310ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 com.github.zafarkhaja.semver.util.UnexpectedElementException; import java.util.EnumSet; import java.util.Iterator; import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*; import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*; /** * A parser for the SemVer Expressions. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public class ExpressionParser implements Parser { /** * The lexer instance used for tokenization of the input string. */ private final Lexer lexer; /** * The stream of tokens produced by the lexer. */ private Stream tokens; /** * Constructs a {@code ExpressionParser} instance * with the corresponding lexer. * * @param lexer the lexer to use for tokenization of the input string */ ExpressionParser(Lexer lexer) { this.lexer = lexer; } /** * Creates and returns new instance of the {@code ExpressionParser} class. *

* This method implements the Static Factory Method pattern. * * @return a new instance of the {@code ExpressionParser} class */ public static Parser newInstance() { return new ExpressionParser(new Lexer()); } /** * Parses the SemVer Expressions. * * @param input a string representing the SemVer Expression * @return the AST for the SemVer Expressions * @throws LexerException when encounters an illegal character * @throws UnexpectedTokenException when consumes a token of an unexpected type */ @Override public Expression parse(String input) { tokens = lexer.tokenize(input); Expression expr = parseSemVerExpression(); consumeNextToken(EOI); return expr; } /** * Parses the {@literal } non-terminal. * *

     * {@literal
     *  ::= "("  ")"
     *                 | "!" "("  ")"
     *                 |  
     *                 | 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseSemVerExpression() { CompositeExpression expr; if (tokens.positiveLookahead(NOT)) { tokens.consume(); consumeNextToken(LEFT_PAREN); expr = not(parseSemVerExpression()); consumeNextToken(RIGHT_PAREN); } else if (tokens.positiveLookahead(LEFT_PAREN)) { consumeNextToken(LEFT_PAREN); expr = parseSemVerExpression(); consumeNextToken(RIGHT_PAREN); } else { expr = parseRange(); } return parseMoreExpressions(expr); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::=   | epsilon
     * }
     * 
* * @param expr the left-hand expression of the logical operators * @return the expression AST */ private CompositeExpression parseMoreExpressions(CompositeExpression expr) { if (tokens.positiveLookahead(AND)) { tokens.consume(); expr = expr.and(parseSemVerExpression()); } else if (tokens.positiveLookahead(OR)) { tokens.consume(); expr = expr.or(parseSemVerExpression()); } return expr; } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *          | 
     *          | 
     *          | 
     *          | 
     *          | 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseRange() { if (tokens.positiveLookahead(TILDE)) { return parseTildeRange(); } else if (tokens.positiveLookahead(CARET)) { return parseCaretRange(); } else if (isWildcardRange()) { return parseWildcardRange(); } else if (isHyphenRange()) { return parseHyphenRange(); } else if (isPartialVersionRange()) { return parsePartialVersionRange(); } return parseComparisonRange(); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::=   | 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseComparisonRange() { Token token = tokens.lookahead(); CompositeExpression expr; switch (token.type) { case EQUAL: tokens.consume(); expr = eq(parseVersion()); break; case NOT_EQUAL: tokens.consume(); expr = neq(parseVersion()); break; case GREATER: tokens.consume(); expr = gt(parseVersion()); break; case GREATER_EQUAL: tokens.consume(); expr = gte(parseVersion()); break; case LESS: tokens.consume(); expr = lt(parseVersion()); break; case LESS_EQUAL: tokens.consume(); expr = lte(parseVersion()); break; default: expr = eq(parseVersion()); } return expr; } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= "~" 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseTildeRange() { consumeNextToken(TILDE); long major = consumeNextNumeric(); if (!tokens.positiveLookahead(DOT)) { Version lo = Version.of(major); Version hi = lo.nextMajorVersion(); return gte(lo).and(lt(hi)); } consumeNextToken(DOT); long minor = consumeNextNumeric(); if (!tokens.positiveLookahead(DOT)) { Version lo = Version.of(major, minor); Version hi = lo.nextMinorVersion(); return gte(lo).and(lt(hi)); } consumeNextToken(DOT); long patch = consumeNextNumeric(); Version lo = Version.of(major, minor, patch); Version hi = lo.nextMinorVersion(); return gte(lo).and(lt(hi)); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= "^" 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseCaretRange() { consumeNextToken(CARET); long major = consumeNextNumeric(); if (!tokens.positiveLookahead(DOT)) { Version lo = Version.of(major); Version hi = lo.nextMajorVersion(); return gte(lo).and(lt(hi)); } consumeNextToken(DOT); long minor = consumeNextNumeric(); if (!tokens.positiveLookahead(DOT)) { Version lo = Version.of(major, minor); Version hi = major > 0 ? lo.nextMajorVersion() : lo.nextMinorVersion(); return gte(lo).and(lt(hi)); } consumeNextToken(DOT); long patch = consumeNextNumeric(); Version lo = Version.of(major, minor, patch); CompositeExpression gte = gte(lo); if (major > 0) { return gte.and(lt(lo.nextMajorVersion())); } else if (minor > 0) { return gte.and(lt(lo.nextMinorVersion())); } else if (patch > 0) { return gte.and(lt(lo.nextPatchVersion())); } return eq(lo); } /** * Determines if the following version terminals are part * of the {@literal } non-terminal. * * @return {@code true} if the following version terminals are * part of the {@literal } non-terminal or * {@code false} otherwise */ private boolean isWildcardRange() { return isVersionFollowedBy(WILDCARD); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *                    |  "." 
     *                    |  "."  "." 
     *
     *  ::= "*" | "x" | "X"
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseWildcardRange() { if (tokens.positiveLookahead(WILDCARD)) { tokens.consume(); return gte(Version.of(0, 0, 0)); } long major = consumeNextNumeric(); consumeNextToken(DOT); if (tokens.positiveLookahead(WILDCARD)) { tokens.consume(); Version lo = Version.of(major); Version hi = lo.nextMajorVersion(); return gte(lo).and(lt(hi)); } long minor = consumeNextNumeric(); consumeNextToken(DOT); consumeNextToken(WILDCARD); Version lo = Version.of(major, minor); Version hi = lo.nextMinorVersion(); return gte(lo).and(lt(hi)); } /** * Determines if the following version terminals are * part of the {@literal } non-terminal. * * @return {@code true} if the following version terminals are * part of the {@literal } non-terminal or * {@code false} otherwise */ private boolean isHyphenRange() { return isVersionFollowedBy(HYPHEN); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::=  "-" 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parseHyphenRange() { CompositeExpression gte = gte(parseVersion()); consumeNextToken(HYPHEN); return gte.and(lte(parseVersion())); } /** * Determines if the following version terminals are part * of the {@literal } non-terminal. * * @return {@code true} if the following version terminals are part * of the {@literal } non-terminal or * {@code false} otherwise */ private boolean isPartialVersionRange() { if (!tokens.positiveLookahead(NUMERIC)) { return false; } EnumSet expected = EnumSet.complementOf(EnumSet.of(NUMERIC, DOT)); return tokens.positiveLookaheadUntil(5, expected.toArray(new Token.Type[0])); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::=  |  "." 
     * }
     * 
* * @return the expression AST */ private CompositeExpression parsePartialVersionRange() { long major = consumeNextNumeric(); if (!tokens.positiveLookahead(DOT)) { Version lo = Version.of(major); Version hi = lo.nextMajorVersion(); return gte(lo).and(lt(hi)); } consumeNextToken(DOT); long minor = consumeNextNumeric(); Version lo = Version.of(major, minor); Version hi = lo.nextMinorVersion(); return gte(lo).and(lt(hi)); } /** * Parses the {@literal } non-terminal. * *
     * {@literal
     *  ::= 
     *             |  "." 
     *             |  "."  "." 
     * }
     * 
* * @return the parsed version */ private Version parseVersion() { long major = consumeNextNumeric(); long minor = 0; if (tokens.positiveLookahead(DOT)) { tokens.consume(); minor = consumeNextNumeric(); } long patch = 0; if (tokens.positiveLookahead(DOT)) { tokens.consume(); patch = consumeNextNumeric(); } return Version.of(major, minor, patch); } /** * Determines if the version terminals are * followed by the specified token type. *

* This method is essentially a {@code lookahead(k)} method * which allows to solve the grammar's ambiguities. * * @param type the token type to check * @return {@code true} if the version terminals are followed by * the specified token type or {@code false} otherwise */ 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); } /** * Tries to consume the next token in the stream. * * @param expected the expected types of the next token * @return the next token in the stream * @throws UnexpectedTokenException when encounters an unexpected token type */ private Token consumeNextToken(Token.Type... expected) { try { return tokens.consume(expected); } catch (UnexpectedElementException e) { throw new UnexpectedTokenException(e); } } private long consumeNextNumeric() { return Long.parseLong(consumeNextToken(NUMERIC).lexeme); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Greater.java000066400000000000000000000043411455073306400265670ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "greater than" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Greater implements Expression { /** * The parsed version, the right-hand * operand of the "greater than" operator. */ private final Version parsedVersion; /** * Constructs a {@code Greater} expression with the parsed version. * * @param parsedVersion the parsed version */ Greater(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version is greater than the parsed version. * * @param version the version to compare to, the left-hand * operand of the "greater than" operator * @return {@code true} if the version is greater than the * parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return version.isHigherThan(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/GreaterOrEqual.java000066400000000000000000000045071455073306400300640ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "greater than or equal to" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class GreaterOrEqual implements Expression { /** * The parsed version, the right-hand operand * of the "greater than or equal to" operator. */ private final Version parsedVersion; /** * Constructs a {@code GreaterOrEqual} expression with the parsed version. * * @param parsedVersion the parsed version */ GreaterOrEqual(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version is greater * than or equal to the parsed version. * * @param version the version to compare to, the left-hand operand * of the "greater than or equal to" operator * @return {@code true} if the version is greater than or equal * to the parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return version.isHigherThanOrEquivalentTo(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Less.java000066400000000000000000000043101455073306400261000ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "less than" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Less implements Expression { /** * The parsed version, the right-hand * operand of the "less than" operator. */ private final Version parsedVersion; /** * Constructs a {@code Less} expression with the parsed version. * * @param parsedVersion the parsed version */ Less(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version is less than the parsed version. * * @param version the version to compare to, the left-hand * operand of the "less than" operator * @return {@code true} if the version is less than the * parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return version.isLowerThan(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/LessOrEqual.java000066400000000000000000000044561455073306400274040ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "less than or equal to" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class LessOrEqual implements Expression { /** * The parsed version, the right-hand operand * of the "less than or equal to" operator. */ private final Version parsedVersion; /** * Constructs a {@code LessOrEqual} expression with the parsed version. * * @param parsedVersion the parsed version */ LessOrEqual(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version is less * than or equal to the parsed version. * * @param version the version to compare to, the left-hand operand * of the "less than or equal to" operator * @return {@code true} if the version is less than or equal * to the parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return version.isLowerThanOrEquivalentTo(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Lexer.java000066400000000000000000000147161455073306400262640ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.util.Stream; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A lexer for the SemVer Expressions. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Lexer { /** * This class holds the information about lexemes in the input stream. */ static class Token { /** * Valid token types. */ enum Type implements Stream.ElementType { NUMERIC("0|[1-9][0-9]*"), DOT("\\."), HYPHEN("-"), EQUAL("="), NOT_EQUAL("!="), GREATER(">(?!=)"), GREATER_EQUAL(">="), LESS("<(?!=)"), LESS_EQUAL("<="), TILDE("~"), WILDCARD("[\\*xX]"), CARET("\\^"), AND("&&?"), OR("\\|\\|?"), NOT("!(?!=)"), LEFT_PAREN("\\("), RIGHT_PAREN("\\)"), WHITESPACE("\\s+"), EOI("?!"); /** * A pattern matching this type. */ final Pattern pattern; /** * Constructs a token type with a regular * expression for the pattern. * * @param regexp the regular expression for the pattern * @see #pattern */ Type(String regexp) { pattern = Pattern.compile("^(" + regexp + ")"); } /** * Returns the string representation of this type. * * @return the string representation of this type */ @Override public String toString() { return name() + "(" + pattern + ")"; } /** * {@inheritDoc} */ @Override public boolean isMatchedBy(Token token) { if (token == null) { return false; } return this == token.type; } } /** * The type of this token. */ final Type type; /** * The lexeme of this token. */ final String lexeme; /** * The position of this token. */ final int position; /** * Constructs a {@code Token} instance * with the type, lexeme and position. * * @param type the type of this token * @param lexeme the lexeme of this token * @param position the position of this token */ Token(Type type, String lexeme, int position) { this.type = type; this.lexeme = (lexeme == null) ? "" : lexeme; this.position = position; } /** * {@inheritDoc} */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof Token)) { return false; } Token token = (Token) other; return type.equals(token.type) && lexeme.equals(token.lexeme) && position == token.position; } /** * {@inheritDoc} */ @Override public int hashCode() { int hash = 5; hash = 71 * hash + type.hashCode(); hash = 71 * hash + lexeme.hashCode(); hash = 71 * hash + position; return hash; } /** * Returns the string representation of this token. * * @return the string representation of this token */ @Override public String toString() { return String.format( "%s(%s) at position %d", type.name(), lexeme, position ); } } /** * Constructs a {@code Lexer} instance. */ Lexer() { } /** * Tokenizes the specified input string. * * @param input the input string to tokenize * @return a stream of tokens * @throws LexerException when encounters an illegal character */ Stream tokenize(String input) { List tokens = new ArrayList<>(); int tokenPos = 0; while (!input.isEmpty()) { boolean matched = false; for (Token.Type tokenType : Token.Type.values()) { Matcher matcher = tokenType.pattern.matcher(input); if (matcher.find()) { matched = true; input = matcher.replaceFirst(""); if (tokenType != Token.Type.WHITESPACE) { tokens.add(new Token( tokenType, matcher.group(), tokenPos )); } tokenPos += matcher.end(); break; } } if (!matched) { throw new LexerException(input); } } tokens.add(new Token(Token.Type.EOI, null, tokenPos)); return new Stream<>(tokens.toArray(new Token[0])); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/LexerException.java000066400000000000000000000042251455073306400301350ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.ParseException; /** * Thrown during the lexical analysis when * an illegal character is encountered. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public class LexerException extends ParseException { private static final long serialVersionUID = 0L; /** * The string being analyzed starting from an illegal character. */ private final String expr; /** * Constructs a {@code LexerException} instance with * a string starting from an illegal character. * * @param expr the string starting from an illegal character */ LexerException(String expr) { super("Illegal character near '" + expr + "'"); this.expr = expr; } /** * Returns the string representation of this exception. * * @return the string representation of this exception */ @Override public String toString() { return getMessage(); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Not.java000066400000000000000000000040101455073306400257270ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the logical "negation" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Not implements Expression { /** * The expression to negate. */ private final Expression expr; /** * Constructs a {@code Not} expression with an expression to negate. * * @param expr the expression to negate */ Not(Expression expr) { this.expr = expr; } /** * Negates the given expression. * * @param version the version to interpret against * @return {@code true} if the given expression evaluates to * {@code false} and {@code false} otherwise */ @Override public boolean interpret(Version version) { return !expr.interpret(version); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/NotEqual.java000066400000000000000000000043271455073306400267320ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the comparison "not equal" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class NotEqual implements Expression { /** * The parsed version, the right-hand operand of the "not equal" operator. */ private final Version parsedVersion; /** * Constructs a {@code NotEqual} expression with the parsed version. * * @param parsedVersion the parsed version */ NotEqual(Version parsedVersion) { this.parsedVersion = parsedVersion; } /** * Checks if the current version does not equal the parsed version. * * @param version the version to compare with, the left-hand * operand of the "not equal" operator * @return {@code true} if the version does not equal the * parsed version or {@code false} otherwise */ @Override public boolean interpret(Version version) { return !version.isEquivalentTo(parsedVersion); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/Or.java000066400000000000000000000044461455073306400255640ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; /** * Expression for the logical "or" operator. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ class Or implements Expression { /** * The left-hand operand of expression. */ private final Expression left; /** * The right-hand operand of expression. */ private final Expression right; /** * Constructs a {@code Or} expression with * the left-hand and right-hand operands. * * @param left the left-hand operand of expression * @param right the right-hand operand of expression */ Or(Expression left, Expression right) { this.left = left; this.right = right; } /** * Checks if one of the operands evaluates to {@code true}. * * @param version the version to interpret against * @return {@code true} if one of the operands evaluates to {@code true} * or {@code false} otherwise */ @Override public boolean interpret(Version version) { return left.interpret(version) || right.interpret(version); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/UnexpectedTokenException.java000066400000000000000000000072041455073306400321630ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.ParseException; import com.github.zafarkhaja.semver.expr.Lexer.Token; import com.github.zafarkhaja.semver.util.UnexpectedElementException; import java.util.Arrays; /** * Thrown when a token of unexpected types is encountered during the parsing. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public class UnexpectedTokenException extends ParseException { private static final long serialVersionUID = 0L; /** * The unexpected token. */ private final Token unexpected; /** * The array of the expected token types. */ private final Token.Type[] expected; /** * Constructs a {@code UnexpectedTokenException} instance with * the wrapped {@code UnexpectedElementException} exception. * * @param cause the wrapped exception */ UnexpectedTokenException(UnexpectedElementException cause) { this( (Token) cause.getUnexpectedElement(), (Token.Type[]) cause.getExpectedElementTypes() ); } /** * Constructs a {@code UnexpectedTokenException} instance * with the unexpected token and the expected types. * * @param unexpected the unexpected token * @param expected an array of the expected token types */ UnexpectedTokenException(Token unexpected, Token.Type... expected) { super(createMessage(unexpected, expected)); this.unexpected = unexpected; this.expected = expected; } /** * Gets the unexpected token. * * @return the unexpected token */ Token getUnexpectedToken() { return unexpected; } /** * Gets the expected token types. * * @return an array of expected token types */ Token.Type[] getExpectedTokenTypes() { return expected; } /** * Returns the string representation of this exception * containing the information about the unexpected * token and, if available, about the expected types. * * @return the string representation of this exception */ @Override public String toString() { return getMessage(); } private static String createMessage(Token unexpected, Token.Type... expected) { String msg = String.format("Unexpected token %s", unexpected); if (expected.length > 0) { msg += String.format(", expecting %s", Arrays.toString(expected)); } return msg; } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/expr/package-info.java000066400000000000000000000027611455073306400275260ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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. */ /** * This package contains classes that implement the SemVer Expressions. *

* The main class of the package is the {@code ExpressionParser} class which * parses the specified expressions and returns the Abstract Syntax Tree. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ package com.github.zafarkhaja.semver.expr; jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/package-info.java000066400000000000000000000027501455073306400265460ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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. */ /** * This is the root package of the Java SemVer library. *

* The package exports most of the public API. The main entry point of the * package is the {@code Version} class, which implements the Facade design * pattern. * * @author Zafar Khaja {@literal } * @since 0.1.0 */ package com.github.zafarkhaja.semver; jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/util/000077500000000000000000000000001455073306400243305ustar00rootroot00000000000000jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/util/Stream.java000066400000000000000000000220461455073306400264320ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.util; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; /** * A simple stream class used to represent a stream of characters or tokens. * * @param the type of elements held in this stream * * @author Zafar Khaja {@literal } * @since 0.7.0 */ public class Stream implements Iterable { /** * The {@code ElementType} interface represents types of the elements * held by this stream and can be used for stream filtering. * * @param type of elements held by this stream */ public interface ElementType { /** * Checks if the specified element matches this type. * * @param element the element to be tested * @return {@code true} if the element matches this type * or {@code false} otherwise */ boolean isMatchedBy(E element); } /** * The array holding all the elements of this stream. */ private final E[] elements; /** * The current offset which is incremented when an element is consumed. * * @see #consume() */ private int offset = 0; /** * Constructs a stream containing the specified elements. *

* The stream does not store the real elements but the defensive copy. * * @param elements the elements to be streamed */ public Stream(E[] elements) { this.elements = elements.clone(); } /** * Consumes the next element in this stream. * * @return the next element in this stream * or {@code null} if no more elements left */ public E consume() { if (offset >= elements.length) { return null; } return elements[offset++]; } /** * Consumes the next element in this stream * only if it is of the expected types. * * @param represents the element type of this stream, removes the * "unchecked generic array creation for varargs parameter" * warnings * @param expected the types which are expected * @return the next element in this stream * @throws UnexpectedElementException if the next element is of an unexpected type */ @SafeVarargs public final > E consume(T... expected) { E lookahead = lookahead(1); for (ElementType type : expected) { if (type.isMatchedBy(lookahead)) { return consume(); } } throw new UnexpectedElementException(lookahead, offset, expected); } /** * Pushes back one element at a time. */ public void pushBack() { if (offset > 0) { offset--; } } /** * Returns the next element in this stream without consuming it. * * @return the next element in this stream */ public E lookahead() { return lookahead(1); } /** * Returns the element at the specified position * in this stream without consuming it. * * @param position the position of the element to return * @return the element at the specified position * or {@code null} if no more elements left */ public E lookahead(int position) { int idx = offset + position - 1; if (idx < elements.length) { return elements[idx]; } return null; } /** * Returns the current offset of this stream. * * @return the current offset of this stream */ public int currentOffset() { return offset; } /** * Checks if the next element in this stream is of the expected types. * * @param represents the element type of this stream, removes the * "unchecked generic array creation for varargs parameter" * warnings * @param expected the expected types * @return {@code true} if the next element is of the expected types * or {@code false} otherwise */ @SafeVarargs public final > boolean positiveLookahead(T... expected) { for (ElementType type : expected) { if (type.isMatchedBy(lookahead(1))) { return true; } } return false; } /** * Checks if there exists an element in this stream of * the expected types before the specified type. * * @param represents the element type of this stream, removes the * "unchecked generic array creation for varargs parameter" * warnings * @param before the type before which to search * @param expected the expected types * @return {@code true} if there is an element of the expected types * before the specified type or {@code false} otherwise */ @SafeVarargs public final > boolean positiveLookaheadBefore( ElementType before, T... expected ) { E lookahead; for (int i = 1; i <= elements.length; i++) { lookahead = lookahead(i); if (before.isMatchedBy(lookahead)) { break; } for (ElementType type : expected) { if (type.isMatchedBy(lookahead)) { return true; } } } return false; } /** * Checks if there is an element in this stream of * the expected types until the specified position. * * @param represents the element type of this stream, removes the * "unchecked generic array creation for varargs parameter" * warnings * @param until the position until which to search * @param expected the expected types * @return {@code true} if there is an element of the expected types * until the specified position or {@code false} otherwise */ @SafeVarargs public final > boolean positiveLookaheadUntil( int until, T... expected ) { for (int i = 1; i <= until; i++) { for (ElementType type : expected) { if (type.isMatchedBy(lookahead(i))) { return true; } } } return false; } /** * Returns an iterator over elements that are left in this stream. * * @return an iterator of the remaining elements in this stream */ @Override public Iterator iterator() { return new Iterator() { /** * The index to indicate the current position * of this iterator. *

* The starting point is set to the current * value of this stream's offset, so that it * doesn't iterate over consumed elements. */ private int index = offset; /** * {@inheritDoc} */ @Override public boolean hasNext() { return index < elements.length; } /** * {@inheritDoc} */ @Override public E next() { if (index >= elements.length) { throw new NoSuchElementException(); } return elements[index++]; } /** * {@inheritDoc} */ @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Returns an array containing all of the * elements that are left in this stream. *

* The returned array is a safe copy. * * @return an array containing all of elements in this stream */ public E[] toArray() { return Arrays.copyOfRange(elements, offset, elements.length); } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/util/UnexpectedElementException.java000066400000000000000000000073311455073306400324740ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.util; import com.github.zafarkhaja.semver.util.Stream.ElementType; import java.util.Arrays; /** * Thrown when attempting to consume a stream element of unexpected types. * * @author Zafar Khaja {@literal } * @see Stream#consume(Stream.ElementType...) * @since 0.7.0 */ public class UnexpectedElementException extends RuntimeException { private static final long serialVersionUID = 0L; /** * The unexpected element in the stream. */ private final Object unexpected; /** * The position of the unexpected element in the stream. */ private final int position; /** * The array of the expected element types. */ private final ElementType[] expected; /** * Constructs a {@code UnexpectedElementException} instance * with the unexpected element and the expected types. * * @param unexpected the unexpected element in the stream * @param position the position of the unexpected element * @param expected an array of the expected element types */ UnexpectedElementException(Object unexpected, int position, ElementType... expected) { super(createMessage(unexpected, position, expected)); this.unexpected = unexpected; this.position = position; this.expected = expected; } /** * Gets the unexpected element. * * @return the unexpected element */ public Object getUnexpectedElement() { return unexpected; } /** * Gets the position of the unexpected element. * * @return the position of the unexpected element */ public int getPosition() { return position; } /** * Gets the expected element types. * * @return an array of expected element types */ public ElementType[] getExpectedElementTypes() { return expected; } /** * Returns the string representation of this exception * containing the information about the unexpected * element and, if available, about the expected types. * * @return the string representation of this exception */ @Override public String toString() { return getMessage(); } private static String createMessage(Object unexpected, int position, ElementType... expected) { String msg = String.format("Unexpected element '%s' at position %d", unexpected, position); if (expected.length > 0) { msg += String.format(", expecting %s", Arrays.toString(expected)); } return msg; } } jsemver-0.10.2/src/main/java/com/github/zafarkhaja/semver/util/package-info.java000066400000000000000000000025011455073306400275150ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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. */ /** * This package provides some useful utility classes. * * @author Zafar Khaja {@literal } * @since 0.7.0 */ package com.github.zafarkhaja.semver.util; jsemver-0.10.2/src/main/resources/000077500000000000000000000000001455073306400170015ustar00rootroot00000000000000jsemver-0.10.2/src/main/resources/META-INF/000077500000000000000000000000001455073306400201415ustar00rootroot00000000000000jsemver-0.10.2/src/main/resources/META-INF/LICENSE000066400000000000000000000021111455073306400211410ustar00rootroot00000000000000The MIT License Copyright 2012-2024 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. jsemver-0.10.2/src/test/000077500000000000000000000000001455073306400150225ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/000077500000000000000000000000001455073306400157435ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/000077500000000000000000000000001455073306400165215ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/000077500000000000000000000000001455073306400200035ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/zafarkhaja/000077500000000000000000000000001455073306400221055ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/000077500000000000000000000000001455073306400234065ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/ParserErrorHandlingTest.java000066400000000000000000000113271455073306400310300ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import com.github.zafarkhaja.semver.VersionParser.CharType; import java.util.Arrays; import java.util.Collection; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static com.github.zafarkhaja.semver.VersionParser.CharType.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class ParserErrorHandlingTest { @ParameterizedTest @MethodSource("parameters") void shouldCorrectlyHandleParseErrors( String invalidVersion, Character unexpected, int position, CharType[] expected ) { try { VersionParser.parseValidSemVer(invalidVersion); } catch (UnexpectedCharacterException e) { assertNotNull(e.getMessage()); assertEquals(unexpected, e.getUnexpectedCharacter()); assertEquals(position, e.getPosition()); assertArrayEquals(expected, e.getExpectedCharTypes()); return; } catch (ParseException e) { if (e.getCause() != null) { UnexpectedCharacterException cause = (UnexpectedCharacterException) e.getCause(); assertEquals(unexpected, cause.getUnexpectedCharacter()); assertEquals(position, cause.getPosition()); assertArrayEquals(expected, cause.getExpectedCharTypes()); } return; } fail("Uncaught exception"); } static Collection parameters() { return Arrays.asList(new Object[][] { { "1", null, 1, new CharType[] { DOT } }, { "1 ", ' ', 1, new CharType[] { DOT } }, { "1.", null, 2, new CharType[] { DIGIT } }, { "1.2", null, 3, new CharType[] { DOT } }, { "1.2.", null, 4, new CharType[] { DIGIT } }, { "a.b.c", 'a', 0, new CharType[] { DIGIT } }, { "1.b.c", 'b', 2, new CharType[] { DIGIT } }, { "1.2.c", 'c', 4, new CharType[] { DIGIT } }, { "!.2.3", '!', 0, new CharType[] { DIGIT } }, { "1.!.3", '!', 2, new CharType[] { DIGIT } }, { "1.2.!", '!', 4, new CharType[] { DIGIT } }, { "v1.2.3", 'v', 0, new CharType[] { DIGIT } }, { "1.2.3-", null, 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2. 3", ' ', 4, new CharType[] { DIGIT } }, { "1.2.3=alpha", '=', 5, new CharType[] { HYPHEN, PLUS, EOI } }, { "1.2.3~beta", '~', 5, new CharType[] { HYPHEN, PLUS, EOI } }, { "1.2.3-be$ta", '$', 8, new CharType[] { PLUS, EOI } }, { "1.2.3+b1+b2", '+', 8, new CharType[] { EOI } }, { "1.2.3-rc!", '!', 8, new CharType[] { PLUS, EOI } }, { "1.2.3-+", '+', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3+@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-rc.", null, 9, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3+b.", null, 8, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-b.+b", '+', 8, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-rc..", '.', 9, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-a+b..", '.', 10, new CharType[] { DIGIT, LETTER, HYPHEN } }, }); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/VersionParserCharTypeTest.java000066400000000000000000000070401455073306400313540ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import com.github.zafarkhaja.semver.VersionParser.CharType; import org.junit.jupiter.api.Test; import static com.github.zafarkhaja.semver.VersionParser.CharType.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class VersionParserCharTypeTest { @Test void shouldBeMatchedByDigit() { assertTrue(DIGIT.isMatchedBy('0')); assertTrue(DIGIT.isMatchedBy('9')); assertFalse(DIGIT.isMatchedBy('a')); assertFalse(DIGIT.isMatchedBy('A')); } @Test void shouldBeMatchedByLetter() { assertTrue(LETTER.isMatchedBy('a')); assertTrue(LETTER.isMatchedBy('A')); assertFalse(LETTER.isMatchedBy('0')); assertFalse(LETTER.isMatchedBy('9')); } @Test void shouldBeMatchedByDot() { assertTrue(DOT.isMatchedBy('.')); assertFalse(DOT.isMatchedBy('-')); assertFalse(DOT.isMatchedBy('0')); assertFalse(DOT.isMatchedBy('9')); } @Test void shouldBeMatchedByHyphen() { assertTrue(HYPHEN.isMatchedBy('-')); assertFalse(HYPHEN.isMatchedBy('+')); assertFalse(HYPHEN.isMatchedBy('a')); assertFalse(HYPHEN.isMatchedBy('0')); } @Test void shouldBeMatchedByPlus() { assertTrue(PLUS.isMatchedBy('+')); assertFalse(PLUS.isMatchedBy('-')); assertFalse(PLUS.isMatchedBy('a')); assertFalse(PLUS.isMatchedBy('0')); } @Test void shouldBeMatchedByEol() { assertTrue(EOI.isMatchedBy(null)); assertFalse(EOI.isMatchedBy('-')); assertFalse(EOI.isMatchedBy('a')); assertFalse(EOI.isMatchedBy('0')); } @Test void shouldBeMatchedByIllegal() { assertTrue(ILLEGAL.isMatchedBy('!')); assertFalse(ILLEGAL.isMatchedBy('-')); assertFalse(ILLEGAL.isMatchedBy('a')); assertFalse(ILLEGAL.isMatchedBy('0')); } @Test void shouldReturnCharTypeForCharacter() { assertEquals(DIGIT, CharType.forCharacter('1')); assertEquals(LETTER, CharType.forCharacter('a')); assertEquals(DOT, CharType.forCharacter('.')); assertEquals(HYPHEN, CharType.forCharacter('-')); assertEquals(PLUS, CharType.forCharacter('+')); assertEquals(EOI, CharType.forCharacter(null)); assertEquals(ILLEGAL, CharType.forCharacter('!')); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/VersionParserTest.java000066400000000000000000000123051455073306400277140ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class VersionParserTest { @Test void shouldParseNormalVersion() { Version version = VersionParser.parseVersionCore("1.0.0"); assertEquals(Version.of(1, 0, 0), version); } @Test void shouldRaiseErrorIfNumericIdentifierHasLeadingZeroes() { assertThrows( ParseException.class, () -> VersionParser.parseVersionCore("01.1.0"), "Numeric identifier MUST NOT contain leading zeroes" ); } @Test void shouldParsePreReleaseVersion() { String[] preRelease = VersionParser.parsePreRelease("beta-1.1"); assertArrayEquals(new String[] {"beta-1", "1"}, preRelease); } @Test void shouldNotAllowDigitsInPreReleaseVersion() { assertThrows( ParseException.class, () -> VersionParser.parsePreRelease("alpha.01"), "Should not allow digits in pre-release version" ); } @Test void shouldRaiseErrorForEmptyPreReleaseIdentifier() { assertThrows( ParseException.class, () -> VersionParser.parsePreRelease("beta-1..1"), "Identifiers MUST NOT be empty" ); } @Test void shouldParseBuildMetadata() { String[] build = VersionParser.parseBuild("build.1"); assertArrayEquals(new String[] {"build", "1"}, build); } @Test void shouldAllowDigitsInBuildMetadata() { assertDoesNotThrow( () -> VersionParser.parseBuild("build.01"), "Should allow digits in build metadata" ); } @Test void shouldRaiseErrorForEmptyBuildIdentifier() { assertThrows( ParseException.class, () -> VersionParser.parseBuild(".build.01"), "Identifiers MUST NOT be empty" ); } @Test void shouldParseValidSemVer() { VersionParser parser = new VersionParser("1.0.0-rc.2+build.05"); Version v = parser.parse(null); assertEquals(Version.of(1, 0, 0, "rc.2", "build.05"), v); } @Test void shouldParseShortVersionCoresWithMajorAndMinorVersionsInLenientMode() { VersionParser parser = new VersionParser("1.2-rc.2+build.05", false); Version v = parser.parse(null); assertEquals(Version.of(1, 2, 0, "rc.2", "build.05"), v); } @Test void shouldParseShortVersionCoresWithMajorVersionInLenientMode() { VersionParser parser = new VersionParser("1-rc.2+build.05", false); Version v = parser.parse(null); assertEquals(Version.of(1, 0, 0, "rc.2", "build.05"), v); } @ParameterizedTest @ValueSource(strings = {"1", "1.2"}) void shouldNotAllowShortVersionCoresInStrictMode(String s) { VersionParser parser = new VersionParser(s, true); assertThrows(ParseException.class, () -> parser.parse(null)); } @Test void shouldRaiseErrorForIllegalInputString() { for (String illegal : new String[] { "", null }) { assertThrows( IllegalArgumentException.class, () -> new VersionParser(illegal), "Should raise error for illegal input string" ); } } @Test void shouldSupportLongNumericIdentifiers() { long l = Long.MAX_VALUE; String version = l + "." + l + "." + l + "-" + l + "+" + l; Version expected = Version.of(l, l, l, String.valueOf(l), String.valueOf(l)); Version actual = VersionParser.parseValidSemVer(version); assertEquals(expected, actual); } @Test void shouldCheckForNumericIdentifierOverflows() { // Long.MAX_VALUE == 9223372036854775807L; Exception e = assertThrows(ParseException.class, () -> Version.parse("1.0.0-9223372036854775808")); assertEquals("Numeric identifier overflow", e.getMessage()); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/VersionTest.java000066400000000000000000001457321455073306400265520ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Locale; import java.util.function.Predicate; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class VersionTest { @Nested class Builder { @Test void shouldSetVersionCore() { Version.Builder b = new Version.Builder(); b.setVersionCore(1, 2, 3); assertEquals(Version.of(1, 2, 3), b.build()); } @Test void shouldNotAcceptNegativeNumbersForVersionCore() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setVersionCore(-1, 2, 3)); assertThrowsIllegalArgumentException(() -> b.setVersionCore( 1, -2, 3)); assertThrowsIllegalArgumentException(() -> b.setVersionCore( 1, 2, -3)); } @Test void shouldSetVersionCoreWithDefaultMinorAndPatchValues() { Version.Builder b = new Version.Builder(); b.setVersionCore(1); assertEquals(Version.of(1, 0, 0), b.build()); } @Test void shouldNotAcceptNegativeNumbersForVersionCoreWithDefaultMinorAndPatchValues() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setVersionCore(-1)); } @Test void shouldSetVersionCoreWithDefaultPatchValue() { Version.Builder b = new Version.Builder(); b.setVersionCore(1, 2); assertEquals(Version.of(1, 2, 0), b.build()); } @Test void shouldNotAcceptNegativeNumbersForVersionCoreWithDefaultPatchValue() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setVersionCore(-1, 2)); assertThrowsIllegalArgumentException(() -> b.setVersionCore(1, -2)); } @Test void shouldSetMajorVersion() { Version.Builder b = new Version.Builder(); b.setMajorVersion(1); Version v = b.build(); assertEquals(1, v.majorVersion()); } @Test void shouldNotAcceptNegativeNumbersForMajorVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setMajorVersion(-1)); } @Test void shouldSetMinorVersion() { Version.Builder b = new Version.Builder(); b.setMinorVersion(2); Version v = b.build(); assertEquals(2, v.minorVersion()); } @Test void shouldNotAcceptNegativeNumbersForMinorVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setMinorVersion(-2)); } @Test void shouldSetPatchVersion() { Version.Builder b = new Version.Builder(); b.setPatchVersion(3); Version v = b.build(); assertEquals(3, v.patchVersion()); } @Test void shouldNotAcceptNegativeNumbersForPatchVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setPatchVersion(-3)); } @Test void shouldSetPreReleaseVersion() { Version.Builder b = new Version.Builder(); b.setPreReleaseVersion("pre", "release"); Version v = b.build(); assertEquals("pre.release", v.preReleaseVersion().get()); } @Test void shouldMakeDefensiveCopyOfArgumentsWhenSettingPreReleaseVersion() { Version.Builder b = new Version.Builder(); String[] ids = {"pre.release"}; b.setPreReleaseVersion(ids); ids[0] = null; Version v = b.build(); assertEquals("pre.release", v.preReleaseVersion().get()); } @Test void shouldNotAcceptNullsForPreReleaseVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setPreReleaseVersion((String[]) null)); assertThrowsIllegalArgumentException(() -> b.setPreReleaseVersion((String) null)); } @Test void shouldNotAcceptEmptyArraysForPreReleaseVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setPreReleaseVersion(new String[0])); assertThrowsIllegalArgumentException(() -> b.setPreReleaseVersion()); } @Test void shouldAddPreReleaseIdentifiers() { Version.Builder b = new Version.Builder(); b.setPreReleaseVersion("pre"); b.addPreReleaseIdentifiers("release", "1"); Version v = b.build(); assertEquals("pre.release.1", v.preReleaseVersion().get()); } @Test void shouldMakeDefensiveCopyOfArgumentsWhenAddingPreReleaseIdentifiers() { Version.Builder b = new Version.Builder(); b.setPreReleaseVersion("pre"); String[] ids = {"release"}; b.addPreReleaseIdentifiers(ids); ids[0] = null; Version v = b.build(); assertEquals("pre.release", v.preReleaseVersion().get()); } @Test void shouldNotAcceptNullsWhenAddingPreReleaseIdentifiers() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.addPreReleaseIdentifiers((String[]) null)); assertThrowsIllegalArgumentException(() -> b.addPreReleaseIdentifiers((String) null)); } @Test void shouldNotAcceptEmptyArraysWhenAddingPreReleaseIdentifiers() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.addPreReleaseIdentifiers(new String[0])); assertThrowsIllegalArgumentException(() -> b.addPreReleaseIdentifiers()); } @Test void shouldUnsetPreReleaseVersion() { Version.Builder b = new Version.Builder(); b.setPreReleaseVersion("pre-release"); b.unsetPreReleaseVersion(); Version v = b.build(); assertFalse(v.preReleaseVersion().isPresent()); } @Test void shouldSetBuildMetadata() { Version.Builder b = new Version.Builder(); b.setBuildMetadata("build", "metadata"); Version v = b.build(); assertEquals("build.metadata", v.buildMetadata().get()); } @Test void shouldMakeDefensiveCopyOfArgumentsWhenSettingBuildMetadata() { Version.Builder b = new Version.Builder(); String[] ids = {"build.metadata"}; b.setBuildMetadata(ids); ids[0] = null; Version v = b.build(); assertEquals("build.metadata", v.buildMetadata().get()); } @Test void shouldNotAcceptNullsForBuildMetadata() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setBuildMetadata((String[]) null)); assertThrowsIllegalArgumentException(() -> b.setBuildMetadata((String) null)); } @Test void shouldNotAcceptEmptyArraysForBuildMetadata() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setBuildMetadata(new String[0])); assertThrowsIllegalArgumentException(() -> b.setBuildMetadata()); } @Test void shouldAddBuildIdentifiers() { Version.Builder b = new Version.Builder(); b.setBuildMetadata("build"); b.addBuildIdentifiers("metadata", "1"); Version v = b.build(); assertEquals("build.metadata.1", v.buildMetadata().get()); } @Test void shouldMakeDefensiveCopyOfArgumentsWhenAddingBuildIdentifiers() { Version.Builder b = new Version.Builder(); b.setBuildMetadata("build"); String[] ids = {"metadata"}; b.addBuildIdentifiers(ids); ids[0] = null; Version v = b.build(); assertEquals("build.metadata", v.buildMetadata().get()); } @Test void shouldNotAcceptNullsWhenAddingBuildIdentifiers() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.addBuildIdentifiers((String[]) null)); assertThrowsIllegalArgumentException(() -> b.addBuildIdentifiers((String) null)); } @Test void shouldNotAcceptEmptyArraysWhenAddingBuildIdentifiers() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.addBuildIdentifiers(new String[0])); assertThrowsIllegalArgumentException(() -> b.addBuildIdentifiers()); } @Test void shouldUnsetBuildVersion() { Version.Builder b = new Version.Builder(); b.setBuildMetadata("build.metadata"); b.unsetBuildMetadata(); Version v = b.build(); assertFalse(v.buildMetadata().isPresent()); } @Test @SuppressWarnings("deprecation") void shouldSetNormalVersion() { Version.Builder b = new Version.Builder(); b.setNormalVersion("1.2.3"); assertEquals(Version.of(1, 2, 3), b.build()); } @Test @SuppressWarnings("deprecation") void shouldNotAcceptNullsWhenSettingNormalVersion() { Version.Builder b = new Version.Builder(); assertThrowsIllegalArgumentException(() -> b.setNormalVersion(null)); } } @Nested class CoreFunctionality { @Test void shouldNormallyTakeTheFormXDotYDotZWhereXYZAreNonNegativeIntegers() { Version v = Version.parse("1.2.3"); assertEquals(1, v.majorVersion()); assertEquals(2, v.minorVersion()); assertEquals(3, v.patchVersion()); } @Test void mayHavePreReleaseVersionFollowingPatchVersionPrependedWithHyphen() { Version v = Version.parse("1.2.3-pre-release"); assertEquals("pre-release", v.preReleaseVersion().get()); } @Test void mayHaveBuildMetadataFollowingPatchOrPreReleaseVersionPrependedWithPlus() { Version v = Version.parse("1.2.3+build.metadata"); assertEquals("build.metadata", v.buildMetadata().get()); } @Test void shouldParseFullSemVerCompliantVersionStringsInStrictMode() { Version v = Version.parse("1.2.3-pre-release+build.metadata"); assertEquals(Version.of(1, 2, 3, "pre-release", "build.metadata"), v); } @Test void shouldCheckInputStringForNullBeforeParsing() { assertThrows(IllegalArgumentException.class, () -> Version.parse(null)); } @ParameterizedTest @ValueSource(strings = {"1", "1.2"}) void shouldNotAcceptShortVersionCoresInStrictMode(String s) { assertThrows(ParseException.class, () -> Version.parse(s)); } @Test void shouldParseShortVersionCoresWithMajorAndMinorVersionsInLenientMode() { Version v = Version.parse("1.2-pre-release+build.metadata", false); assertEquals(Version.of(1, 2, 0, "pre-release", "build.metadata"), v); } @Test void shouldParseShortVersionCoresWithMajorVersionInLenientMode() { Version v = Version.parse("1-pre-release+build.metadata", false); assertEquals(Version.of(1, 0, 0, "pre-release", "build.metadata"), v); } @Test void shouldTryToParseVersionStringsInStrictMode() { assertTrue(Version.tryParse("1.2.3-rc+abcdefg").isPresent()); assertFalse(Version.tryParse("1.2.3+rc+abcdefg").isPresent()); } @ParameterizedTest @ValueSource(strings = {"1", "1.2"}) void shouldTryToParseShortVersionCoresInLenientMode(String s) { assertTrue(Version.tryParse(s, false).isPresent()); } @Test void shouldCheckValidityOfVersionStringsInStrictMode() { assertTrue(Version.isValid("1.2.3-pre-release+build.metadata")); assertFalse(Version.isValid("1.2.3-pre+release+build.metadata")); } @ParameterizedTest @ValueSource(strings = {"1", "1.2"}) void shouldCheckValidityOfShortVersionCoresInLenientMode(String s) { assertTrue(Version.isValid(s, false)); } @Test void shouldHaveStaticFactoryMethod() { Version v = Version.of(1, 2, 3, "pre-release", "build.metadata"); assertEquals("1.2.3-pre-release+build.metadata", v.toString()); } @Test void shouldNotAcceptNegativeNumbersInStaticFactoryMethod() { assertThrowsIllegalArgumentException(() -> Version.of(-1, 0, 0)); assertThrowsIllegalArgumentException(() -> Version.of(0, -1, 0)); assertThrowsIllegalArgumentException(() -> Version.of(0, 0, -1)); } @ParameterizedTest @ValueSource(strings = {"rc.01", "rc+1", "rc..1", "rc.1+"}) void shouldNotAcceptInvalidPreReleaseVersionInStaticFactoryMethod(String s) { assertThrows(ParseException.class, () -> Version.of(1, s)); } @ParameterizedTest @ValueSource(strings = {"build+1", "build..1", "build.1+"}) void shouldNotAcceptInvalidBuildMetadataInStaticFactoryMethod(String s) { assertThrows(ParseException.class, () -> Version.of(1, null, s)); } @Test void shouldHaveGetters() { Version v = Version.of(1, 2, 3, "pre-release", "build.metadata"); assertEquals(1, v.majorVersion()); assertEquals(2, v.minorVersion()); assertEquals(3, v.patchVersion()); assertEquals("pre-release", v.preReleaseVersion().get()); assertEquals("build.metadata", v.buildMetadata().get()); } @Test void shouldReturnEmptyOptionalIfPreReleaseVersionIsNotSet() { Version v = Version.of(0, 0, 1, null); assertFalse(v.preReleaseVersion().isPresent()); } @Test void shouldReturnEmptyOptionalIfBuildMetadataIsNotSet() { Version v = Version.of(0, 0, 1, null, null); assertFalse(v.buildMetadata().isPresent()); } @Test void shouldObtainNextMajorVersion() { Version v1 = Version.of(1); Version v2 = v1.nextMajorVersion(3); assertEquals(3, v2.majorVersion()); } @Test void shouldNotAcceptNegativeMajorVersion() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.nextMajorVersion(-2)); } @Test void shouldNotAllowToObtainLowerNextMajorVersion() { Version v = Version.of(3); assertThrows(IllegalStateException.class, () -> v.nextMajorVersion(2)); } @Test void shouldNotAllowToObtainEquivalentNextMajorVersion() { Version v = Version.of(3); assertThrows(IllegalStateException.class, () -> v.nextMajorVersion(3)); } @Test void shouldResetMinorVersionWhenIncreasingMajorVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextMajorVersion(4); assertEquals(0, v2.minorVersion()); } @Test void shouldResetPatchVersionWhenIncreasingMajorVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextMajorVersion(4); assertEquals(0, v2.patchVersion()); } @Test void shouldDropPreReleaseVersionWhenIncreasingMajorVersion() { Version v1 = Version.of(1, "pre-release"); Version v2 = v1.nextMajorVersion(2); assertFalse(v2.preReleaseVersion().isPresent()); } @Test void shouldDropBuildMetadataWhenIncreasingMajorVersion() { Version v1 = Version.of(1, null, "build.metadata"); Version v2 = v1.nextMajorVersion(3); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldSpecifyPreReleaseVersionWhenObtainingNextMajorVersion() { Version v1 = Version.of(1); Version v2 = v1.nextMajorVersion(2, "pre-release"); assertEquals("pre-release", v2.preReleaseVersion().get()); } @Test void shouldAcceptSeparateIdentifiersWhenIncreasingMajorVersion() { Version v1 = Version.of(1); Version v2 = v1.nextMajorVersion(3, "pre", "release"); assertEquals("pre.release", v2.preReleaseVersion().get()); } @Test void shouldNotAcceptNullPreReleaseVersionForNextMajorVersion() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.nextMajorVersion(2, (String[]) null)); } @Test void shouldNotAcceptNullIdentifiersForNextMajorVersion() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.nextMajorVersion(2, (String) null)); } @ParameterizedTest @ValueSource(strings = {"01", "rc.", ".rc", "rc!"}) void shouldNotAcceptInvalidIdentifiersForNextMajorVersion(String id) { Version v = Version.of(1); assertThrows(ParseException.class, () -> v.nextMajorVersion(2, id)); } @Test void shouldIncrementMajorVersionByOne() { Version v1 = Version.of(1); Version v2 = v1.nextMajorVersion(); assertEquals(2, v2.majorVersion()); } @Test void shouldNotAllowMajorVersionNumberToOverflow() { Version v = Version.of(Long.MAX_VALUE); assertThrows(ArithmeticException.class, v::nextMajorVersion); } @Test void shouldObtainNextMinorVersion() { Version v1 = Version.of(1, 2); Version v2 = v1.nextMinorVersion(4); assertEquals(4, v2.minorVersion()); } @Test void shouldNotAcceptNegativeMinorVersion() { Version v = Version.of(1, 2); assertThrowsIllegalArgumentException(() -> v.nextMinorVersion(-3)); } @Test void shouldNotAllowToObtainLowerNextMinorVersion() { Version v = Version.of(3, 2); assertThrows(IllegalStateException.class, () -> v.nextMinorVersion(1)); } @Test void shouldNotAllowToObtainEquivalentNextMinorVersion() { Version v = Version.of(3, 2); assertThrows(IllegalStateException.class, () -> v.nextMinorVersion(2)); } @Test void shouldNotChangeMajorVersionWhenIncreasingMinorVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextMinorVersion(3); assertEquals(v1.majorVersion(), v2.majorVersion()); } @Test void shouldResetPatchVersionWhenIncreasingMinorVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextMinorVersion(3); assertEquals(0, v2.patchVersion()); } @Test void shouldDropPreReleaseVersionWhenIncreasingMinorVersion() { Version v1 = Version.of(1, 2, "pre-release"); Version v2 = v1.nextMinorVersion(3); assertFalse(v2.preReleaseVersion().isPresent()); } @Test void shouldDropBuildMetadataWhenIncreasingMinorVersion() { Version v1 = Version.of(1, 2, null, "build.metadata"); Version v2 = v1.nextMinorVersion(4); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldSpecifyPreReleaseVersionWhenObtainingNextMinorVersion() { Version v1 = Version.of(1, 2); Version v2 = v1.nextMinorVersion(3, "pre-release"); assertEquals("pre-release", v2.preReleaseVersion().get()); } @Test void shouldAcceptSeparateIdentifiersWhenIncreasingMinorVersion() { Version v1 = Version.of(1, 2); Version v2 = v1.nextMinorVersion(3, "pre", "release"); assertEquals("pre.release", v2.preReleaseVersion().get()); } @Test void shouldNotAcceptNullPreReleaseVersionForNextMinorVersion() { Version v = Version.of(1, 2); assertThrowsIllegalArgumentException(() -> v.nextMinorVersion(3, (String[]) null)); } @Test void shouldNotAcceptNullIdentifiersForNextMinorVersion() { Version v = Version.of(1, 2); assertThrowsIllegalArgumentException(() -> v.nextMinorVersion(3, (String) null)); } @ParameterizedTest @ValueSource(strings = {"01", "rc.", ".rc", "rc!"}) void shouldNotAcceptInvalidIdentifiersForNextMinorVersion(String id) { Version v = Version.of(1, 2); assertThrows(ParseException.class, () -> v.nextMinorVersion(3, id)); } @Test void shouldIncrementMinorVersionByOne() { Version v1 = Version.of(1, 2); Version v2 = v1.nextMinorVersion(); assertEquals(3, v2.minorVersion()); } @Test void shouldNotAllowMinorVersionNumberToOverflow() { Version v = Version.of(0, Long.MAX_VALUE); assertThrows(ArithmeticException.class, v::nextMinorVersion); } @Test void shouldObtainNextPatchVersion() { Version v1 = Version.of(1, 2, 3); Version v2 = v1.nextPatchVersion(5); assertEquals(5, v2.patchVersion()); } @Test void shouldNotAcceptNegativePatchVersion() { Version v = Version.of(1, 2, 3); assertThrowsIllegalArgumentException(() -> v.nextPatchVersion(-4)); } @Test void shouldNotAllowToObtainLowerNextPatchVersion() { Version v = Version.of(3, 2, 1); assertThrows(IllegalStateException.class, () -> v.nextPatchVersion(0)); } @Test void shouldNotAllowToObtainEquivalentNextPatchVersion() { Version v = Version.of(3, 2, 1); assertThrows(IllegalStateException.class, () -> v.nextPatchVersion(1)); } @Test void shouldNotChangeMajorVersionWhenIncreasingPatchVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextPatchVersion(2); assertEquals(v1.majorVersion(), v2.majorVersion()); } @Test void shouldNotChangeMinorVersionWhenIncreasingPatchVersion() { Version v1 = Version.of(3, 2, 1); Version v2 = v1.nextPatchVersion(2); assertEquals(v1.minorVersion(), v2.minorVersion()); } @Test void shouldDropPreReleaseVersionWhenIncreasingPatchVersion() { Version v1 = Version.of(1, 2, 3, "pre-release"); Version v2 = v1.nextPatchVersion(4); assertFalse(v2.preReleaseVersion().isPresent()); } @Test void shouldDropBuildMetadataWhenIncreasingPatchVersion() { Version v1 = Version.of(1, 2, 3, null, "build.metadata"); Version v2 = v1.nextPatchVersion(5); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldSpecifyPreReleaseVersionWhenObtainingNextPatchVersion() { Version v1 = Version.of(1, 2, 3); Version v2 = v1.nextPatchVersion(4, "pre-release"); assertEquals("pre-release", v2.preReleaseVersion().get()); } @Test void shouldAcceptSeparateIdentifiersWhenIncreasingPatchVersion() { Version v1 = Version.of(1, 2, 3); Version v2 = v1.nextPatchVersion(4, "pre", "release"); assertEquals("pre.release", v2.preReleaseVersion().get()); } @Test void shouldNotAcceptNullPreReleaseVersionForNextPatchVersion() { Version v = Version.of(1, 2, 3); assertThrowsIllegalArgumentException(() -> v.nextPatchVersion(4, (String[]) null)); } @Test void shouldNotAcceptNullIdentifiersForNextPatchVersion() { Version v = Version.of(1, 2, 3); assertThrowsIllegalArgumentException(() -> v.nextPatchVersion(4, (String) null)); } @ParameterizedTest @ValueSource(strings = {"01", "rc.", ".rc", "rc!"}) void shouldNotAcceptInvalidIdentifiersForNextPatchVersion(String id) { Version v = Version.of(1, 2, 3); assertThrows(ParseException.class, () -> v.nextPatchVersion(4, id)); } @Test void shouldIncrementPatchVersionByOne() { Version v1 = Version.of(1, 2, 3); Version v2 = v1.nextPatchVersion(); assertEquals(4, v2.patchVersion()); } @Test void shouldNotAllowPatchVersionNumberToOverflow() { Version v = Version.of(0, 0, Long.MAX_VALUE); assertThrows(ArithmeticException.class, v::nextPatchVersion); } @Test void shouldObtainNextPreReleaseVersion() { Version v1 = Version.of(1, "alpha"); Version v2 = v1.nextPreReleaseVersion("beta"); assertEquals("beta", v2.preReleaseVersion().get()); } @Test void shouldNotAllowToObtainNextPreReleaseVersionOfStableVersion() { Version v = Version.of(1); assertThrows(IllegalStateException.class, () -> v.nextPreReleaseVersion("alpha")); } @Test void shouldNotAcceptNullForNextPreReleaseVersion() { Version v = Version.of(1, "pre-release"); assertThrowsIllegalArgumentException(() -> v.nextPreReleaseVersion((String[]) null)); } @Test void shouldNotAcceptNullIdentifiersForNextPreReleaseVersion() { Version v = Version.of(1, "pre-release"); assertThrowsIllegalArgumentException(() -> v.nextPreReleaseVersion((String) null)); } @ParameterizedTest @ValueSource(strings = {"01", "rc.", ".rc", "rc!"}) void shouldNotAcceptInvalidIdentifiersForNextPreReleaseVersion(String id) { Version v = Version.of(1, "pre-release"); assertThrows(ParseException.class, () -> v.nextPreReleaseVersion(id)); } @Test void shouldNotAllowToObtainLowerNextPreReleaseVersion() { Version v = Version.of(1, "beta"); assertThrows(IllegalStateException.class, () -> v.nextPreReleaseVersion("alpha")); } @Test void shouldNotAllowToObtainEquivalentNextPreReleaseVersion() { Version v = Version.of(1, "beta"); assertThrows(IllegalStateException.class, () -> v.nextPreReleaseVersion("beta")); } @Test void shouldNotChangeVersionCoreWhenPreReleaseVersionIsIncreased() { Version v1 = Version.of(1, 2, 3, "alpha"); Version v2 = v1.nextPreReleaseVersion("beta"); assertEquals(v1.majorVersion(), v2.majorVersion()); assertEquals(v1.minorVersion(), v2.minorVersion()); assertEquals(v1.patchVersion(), v2.patchVersion()); } @Test void shouldDropBuildMetadataWhenPreReleaseVersionIsIncreased() { Version v1 = Version.of(1, "alpha", "build.metadata"); Version v2 = v1.nextPreReleaseVersion("beta"); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldIncrementPreReleaseVersionByOne() { Version v1 = Version.of(1, "pre-release.1"); Version v2 = v1.nextPreReleaseVersion(); assertEquals("pre-release.2", v2.preReleaseVersion().get()); } @Test void shouldAddNumericIdentifierToPreReleaseVersionIfNeededWhenIncrementing() { Version v1 = Version.of(1, "pre-release"); Version v2 = v1.nextPreReleaseVersion(); assertEquals("pre-release.1", v2.preReleaseVersion().get()); } @Test void shouldNotAllowPreReleaseNumericIdentifierToOverflow() { Version v = Version.of(1, String.valueOf(Long.MAX_VALUE)); assertThrows(ArithmeticException.class, v::nextPreReleaseVersion); } @Test void shouldPromotePreReleaseVersionToStableVersion() { Version v1 = Version.of(1, 2, 3, "pre-release"); Version v2 = v1.toStableVersion(); assertTrue(v2.isStable()); } @Test void shouldNotChangeVersionCoreWhenPromotingToStableVersion() { Version v1 = Version.of(1, 2, 3, "pre-release"); Version v2 = v1.toStableVersion(); assertEquals(v1.majorVersion(), v2.majorVersion()); assertEquals(v1.minorVersion(), v2.minorVersion()); assertEquals(v1.patchVersion(), v2.patchVersion()); } @Test void shouldDropBuildMetadataWhenPromotingToStableVersion() { Version v1 = Version.of(1, "pre-release", "build.metadata"); Version v2 = v1.toStableVersion(); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldSetBuildMetadata() { Version v1 = Version.of(1); Version v2 = v1.withBuildMetadata("build", "metadata"); assertEquals("build.metadata", v2.buildMetadata().get()); } @Test void shouldNotChangeVersionCoreWhenSettingBuildMetadata() { Version v1 = Version.of(1, 2, 3, "pre-release"); Version v2 = v1.withBuildMetadata("build.metadata"); assertEquals(v1.majorVersion(), v2.majorVersion()); assertEquals(v1.minorVersion(), v2.minorVersion()); assertEquals(v1.patchVersion(), v2.patchVersion()); } @Test void shouldNotChangePreReleaseVersionWhenSettingBuildMetadata() { Version v1 = Version.of(1, "pre-release"); Version v2 = v1.withBuildMetadata("build.metadata"); assertEquals(v1.preReleaseVersion().get(), v2.preReleaseVersion().get()); } @Test void shouldNotAcceptNullForBuildMetadata() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.withBuildMetadata((String[]) null)); } @Test void shouldNotAcceptEmptyBuildMetadata() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.withBuildMetadata((new String[0]))); } @Test void shouldNotAcceptNullIdentifiersForBuildMetadata() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.withBuildMetadata((String) null)); } @ParameterizedTest @ValueSource(strings = {"build.", ".build", "build!"}) void shouldNotAcceptInvalidIdentifiersForBuildMetadata(String id) { Version v = Version.of(1); assertThrows(ParseException.class, () -> v.withBuildMetadata(id)); } @Test void shouldDropBuildMetadata() { Version v1 = Version.of(1, null, "build.metadata"); Version v2 = v1.withoutBuildMetadata(); assertFalse(v2.buildMetadata().isPresent()); } @Test void shouldNotChangeVersionCoreWhenDroppingBuildMetadata() { Version v1 = Version.of(1, 2, 3, null, "build.metadata"); Version v2 = v1.withoutBuildMetadata(); assertEquals(v1.majorVersion(), v2.majorVersion()); assertEquals(v1.minorVersion(), v2.minorVersion()); assertEquals(v1.patchVersion(), v2.patchVersion()); } @Test void shouldNotChangePreReleaseVersionWhenDroppingBuildMetadata() { Version v1 = Version.of(1, "pre-release", "build.metadata"); Version v2 = v1.withoutBuildMetadata(); assertEquals(v1.preReleaseVersion().get(), v2.preReleaseVersion().get()); } @Test void shouldBeImmutable() { Version v = Version.of(1, 2, 3, "alpha", "test"); assertNotEquals(v, v.nextMajorVersion()); assertNotEquals(v, v.nextMajorVersion("beta")); assertNotEquals(v, v.nextMinorVersion()); assertNotEquals(v, v.nextMinorVersion("gamma")); assertNotEquals(v, v.nextPatchVersion()); assertNotEquals(v, v.nextPatchVersion("delta")); assertNotEquals(v, v.nextPreReleaseVersion()); assertNotEquals(v, v.nextPreReleaseVersion("epsilon")); assertNotEquals(v, v.withBuildMetadata("build.metadata")); assertNotEquals(v, v.withoutBuildMetadata()); } @Test void shouldCheckIfVersionSatisfiesPredicates() { Version v = Version.of(2, 0, 0, "beta"); assertFalse(v.satisfies(Version::isStable)); assertTrue(v.satisfies(Version::isPublicApiStable)); } @Test void shouldRaiseErrorIfPredicateIsNull() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.satisfies((Predicate) null)); } @Test void shouldCheckIfVersionSatisfiesRangeExpressions() { Version v = Version.of(2, 0, 0, "beta"); assertTrue(v.satisfies(">=1.0.0 & <3.0.0")); assertFalse(v.satisfies(">=2.0.0 & <3.0.0")); } @Test void shouldRaiseErrorIfRangeExpressionIsNull() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.satisfies((String) null)); } @Test void shouldConsiderPreReleaseVersionsAsUnstable() { Version v = Version.of(1, 2, 3, "rc"); assertTrue(v.isPreRelease()); assertFalse(v.isStable()); } @Test void shouldConsiderNonPreReleaseVersionsAsStable() { Version v = Version.of(1, 2, 3); assertFalse(v.isPreRelease()); assertTrue(v.isStable()); } @Test void shouldConsiderPublicApiAsUnstableIfMajorVersionIsZero() { Version v = Version.of(0, 10, 0); assertFalse(v.isPublicApiStable()); } @Test void shouldConsiderPublicApiAsStableIfMajorVersionIsOneOrHigher() { Version v = Version.of(1); assertTrue(v.isPublicApiStable()); } @Test void shouldCheckIfVersionsAreCompatibleInTermsOfPublicApi() { Version v1 = Version.of(1, 1, 1); Version v2 = Version.of(1, 2, 3); Version v3 = Version.of(0, 1, 2); assertTrue(v1.isPublicApiCompatibleWith(v2)); assertFalse(v1.isPublicApiCompatibleWith(v3)); } @Test void shouldCheckIfVersionsAreCompatibleInTermsOfMajorVersions() { Version v1 = Version.of(1, 1, 1); Version v2 = Version.of(1, 2, 3); Version v3 = Version.of(2, 2, 2); assertTrue(v1.isSameMajorVersionAs(v2)); assertFalse(v1.isSameMajorVersionAs(v3)); } @Test void shouldCheckForNullsWhenComparingMajorVersions() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.isSameMajorVersionAs(null)); } @Test void shouldCheckIfVersionsAreCompatibleInTermsOfMajorAndMinorVersions() { Version v1 = Version.of(1, 1, 1); Version v2 = Version.of(1, 1, 2); Version v3 = Version.of(1, 2, 3); assertTrue(v1.isSameMinorVersionAs(v2)); assertFalse(v1.isSameMinorVersionAs(v3)); } @Test void shouldCheckForNullsWhenComparingMajorAndMinorVersions() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.isSameMinorVersionAs(null)); } @Test void shouldCheckIfVersionsAreCompatibleInTermsOfMajorMinorAndPatchVersions() { Version v1 = Version.of(1, 1, 1); Version v2 = Version.of(1, 1, 1); Version v3 = Version.of(1, 1, 2); assertTrue(v1.isSamePatchVersionAs(v2)); assertFalse(v1.isSamePatchVersionAs(v3)); } @Test void shouldCheckForNullsWhenComparingMajorMinorAndPatchVersions() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.isSamePatchVersionAs(null)); } @Test void shouldDetermineIfItsPrecedenceIsHigherThanThatOfOthers() { Version v1 = Version.of(3, 2, 1); Version v2 = Version.of(1, 2, 3); assertTrue(v1.isHigherThan(v2)); assertFalse(v2.isHigherThan(v1)); assertFalse(v1.isHigherThan(v1)); } @Test void shouldDetermineIfItsPrecedenceIsHigherThanOrEqualToThatOfOthers() { Version v1 = Version.of(3, 2, 1); Version v2 = Version.of(1, 2, 3); assertTrue(v1.isHigherThanOrEquivalentTo(v2)); assertFalse(v2.isHigherThanOrEquivalentTo(v1)); assertTrue(v1.isHigherThanOrEquivalentTo(v1)); } @Test void shouldDetermineIfItsPrecedenceIsLowerThanThatOfOthers() { Version v1 = Version.of(3, 2, 1); Version v2 = Version.of(1, 2, 3); assertFalse(v1.isLowerThan(v2)); assertTrue(v2.isLowerThan(v1)); assertFalse(v1.isLowerThan(v1)); } @Test void shouldDetermineIfItsPrecedenceIsLowerThanOrEqualToThatOfOthers() { Version v1 = Version.of(3, 2, 1); Version v2 = Version.of(1, 2, 3); assertFalse(v1.isLowerThanOrEquivalentTo(v2)); assertTrue(v2.isLowerThanOrEquivalentTo(v1)); assertTrue(v1.isLowerThanOrEquivalentTo(v1)); } @Test void shouldDetermineIfItsPrecedenceIsEqualToThatOfOthers() { Version v1 = Version.of(1, 2, 3, "pre-release"); Version v2 = Version.of(1, 2, 3, "pre-release"); Version v3 = Version.of(1, 2, 3); assertTrue(v1.isEquivalentTo(v2)); assertFalse(v1.isEquivalentTo(v3)); } @Test void shouldIgnoreBuildMetadataWhenCheckingForEquivalence() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release"); assertTrue(v1.isEquivalentTo(v2)); } @Test void shouldBeAbleToCompareWithoutBuildMetadata() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata.1"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata.2"); assertTrue(0 > v1.compareTo(v2)); assertTrue(0 == v1.compareToIgnoreBuildMetadata(v2)); } @Test void shouldRaiseErrorIfComparedWithNull() { Version v = Version.of(1); assertThrowsIllegalArgumentException(() -> v.compareToIgnoreBuildMetadata(null)); } @Test void shouldOverrideEqualsMethod() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v3 = Version.of(1, 2, 3, "pre-release"); assertEquals(v1, v2); assertNotEquals(v1, v3); } @Test void shouldConvertToBuilderWithPrepopulatedValues() { Version v = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version.Builder b = v.toBuilder(); assertEquals(b.build(), v); } @Test void shouldNotFailToConvertToBuilderWithoutPreReleaseVersion() { Version v = Version.of(1, 2, 3, null, "build.metadata"); assertDoesNotThrow(v::toBuilder); } @Test void shouldNotFailToConvertToBuilderWithoutBuildMetadata() { Version v = Version.of(1, 2, 3, "pre-release"); assertDoesNotThrow(v::toBuilder); } @Test @SuppressWarnings("deprecation") void shouldSetPreReleaseVersion() { Version v1 = Version.of(1, 0, 0); Version v2 = v1.setPreReleaseVersion("pre-release"); assertEquals("1.0.0-pre-release", v2.toString()); } @Test @SuppressWarnings("deprecation") void shouldDropBuildMetadataWhenSettingPreReleaseVersion() { Version v1 = Version.of(1, 0, 0, "alpha", "build.metadata"); Version v2 = v1.setPreReleaseVersion("beta"); assertEquals("1.0.0-beta", v2.toString()); } @Test @SuppressWarnings("deprecation") void shouldIncrementBuildMetadata() { Version v1 = Version.of(1, 0, 0, null, "build.metadata.1"); Version v2 = v1.incrementBuildMetadata(); assertEquals("1.0.0+build.metadata.2", v2.toString()); } @Test @SuppressWarnings("deprecation") void shouldNotAllowToIncrementEmptyBuildMetadata() { Version v = Version.of(1, 0, 0); assertThrows(IllegalStateException.class, v::incrementBuildMetadata); } } @Nested class CompareToMethod { @Test void shouldCompareMajorVersionNumerically() { Version v = Version.of(22); assertTrue(0 < v.compareTo(Version.of(3))); assertTrue(0 == v.compareTo(Version.of(22))); assertTrue(0 > v.compareTo(Version.of(111))); } @Test void shouldCompareMinorVersionNumerically() { Version v = Version.of(0, 22); assertTrue(0 < v.compareTo(Version.of(0, 3))); assertTrue(0 == v.compareTo(Version.of(0, 22))); assertTrue(0 > v.compareTo(Version.of(0, 111))); } @Test void shouldComparePatchVersionNumerically() { Version v = Version.of(0, 0, 22); assertTrue(0 < v.compareTo(Version.of(0, 0, 3))); assertTrue(0 == v.compareTo(Version.of(0, 0, 22))); assertTrue(0 > v.compareTo(Version.of(0, 0, 111))); } @Test void shouldCompareAlphanumericIdentifiersLexicallyInAsciiOrder() { Version v = Version.of(1, "beta"); assertTrue(0 < v.compareTo(Version.of(1, "alpha"))); assertTrue(0 == v.compareTo(Version.of(1, "beta"))); assertTrue(0 > v.compareTo(Version.of(1, "gamma"))); } @Test void shouldGiveHigherPrecedenceToNonNumericIdentifierThanNumeric() { Version v1 = Version.of(1, "abc"); Version v2 = Version.of(1, "111"); assertTrue(0 < v1.compareTo(v2)); } @Test void shouldCompareNumericIdentifiersNumerically() { Version v = Version.of(1, "22"); assertTrue(0 > v.compareTo(Version.of(1, "111"))); assertTrue(0 == v.compareTo(Version.of(1, "22"))); assertTrue(0 < v.compareTo(Version.of(1, "3"))); } @Test void shouldGiveHigherPrecedenceToVersionWithLargerSetOfIdentifiers() { Version v1 = Version.of(1, "a.b.c"); Version v2 = Version.of(1, "a.b.c.d"); assertTrue(0 > v1.compareTo(v2)); } @Test void shouldGiveHigherPrecedenceToStableVersionThanPreReleaseVersion() { Version v1 = Version.of(1, "pre-release"); Version v2 = Version.of(1); assertTrue(0 > v1.compareTo(v2)); } @Test void shouldGiveHigherPrecedenceToVersionWithBuildMetadata() { Version v1 = Version.of(1, "pre-release", "build.metadata"); Version v2 = Version.of(1, "pre-release"); assertTrue(0 < v1.compareTo(v2)); } @Test void shouldBeConsistentWithEquals() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata"); assertEquals(v1, v2); assertEquals(0, v1.compareTo(v2)); } @Test void shouldCorrectlyCompareVersionsWithBuildMetadata() { String[] versions = { "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.1+build.1", "1.0.0", "1.0.0+0.3.7", "1.3.7+build", "1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a" }; for (int i = 1; i < versions.length; i++) { Version v1 = Version.parse(versions[i-1]); Version v2 = Version.parse(versions[i]); assertTrue(0 > v1.compareTo(v2)); } } } @Nested class IncrementOrderComparator { @Test void shouldSortInIncrementOrder() { Version v1 = Version.of(1, 2, 3); Version v2 = Version.of(3, 2, 1); assertTrue(0 > v1.compareTo(v2)); assertTrue(0 > Version.INCREMENT_ORDER.compare(v1, v2)); } @Test void shouldIgnoreBuildMetadata() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata.1"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata.2"); assertTrue(0 == Version.INCREMENT_ORDER.compare(v1, v2)); } } @Nested class PrecedenceOrderComparator { @Test void shouldSortInPrecedenceOrder() { Version v1 = Version.of(1, 2, 3); Version v2 = Version.of(3, 2, 1); assertTrue(0 > Version.INCREMENT_ORDER.compare(v1, v2)); assertTrue(0 < Version.PRECEDENCE_ORDER.compare(v1, v2)); } @Test void shouldIgnoreBuildMetadata() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata.1"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata.2"); assertTrue(0 == Version.PRECEDENCE_ORDER.compare(v1, v2)); } } @Nested class EqualsMethod { @Test void shouldBeReflexive() { Version v = Version.of(1, 2, 3); assertEquals(v, v); } @Test void shouldBeSymmetric() { Version v1 = Version.of(1, 2, 3); Version v2 = Version.of(1, 2, 3); assertEquals(v1, v2); assertEquals(v2, v1); } @Test void shouldBeTransitive() { Version v1 = Version.of(1, 2, 3); Version v2 = Version.of(1, 2, 3); Version v3 = Version.of(1, 2, 3); assertEquals(v1, v2); assertEquals(v2, v3); assertEquals(v1, v3); } @Test void shouldBeConsistent() { Version v1 = Version.of(1, 2, 3); Version v2 = Version.of(1, 2, 3); assertEquals(v1, v2); assertEquals(v1, v2); } @Test void shouldReturnFalseIfOtherVersionIsOfDifferentType() { Version v1 = Version.of(1, 2, 3); assertNotEquals(v1, "1.2.3"); } @Test void shouldReturnFalseIfOtherVersionIsNull() { Version v1 = Version.of(1, 2, 3); Version v2 = null; assertNotEquals(v1, v2); } @Test void shouldNotIgnoreBuildMetadataWhenCheckingForExactEquality() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release"); assertNotEquals(v1, v2); } } @Nested class HashCodeMethod { @Test void shouldReturnSameHashCodeIfVersionsAreEqual() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release", "build.metadata"); assertEquals(v1, v2); assertEquals(v1.hashCode(), v2.hashCode()); } @Test void shouldReturnDifferentHashCodesIfVersionsAreNotEqual() { Version v1 = Version.of(1, 2, 3, "pre-release", "build.metadata"); Version v2 = Version.of(1, 2, 3, "pre-release"); assertNotEquals(v1, v2); assertNotEquals(v1.hashCode(), v2.hashCode()); } } @Nested class ToStringMethod { @Test void shouldReturnStringRepresentation() { String value = "1.2.3-beta+build"; Version v = Version.parse(value); assertEquals(value, v.toString()); } @Test void shouldUseRootLocale() { Locale.setDefault(new Locale("hi", "IN")); Version v = Version.of(1, 2, 3); assertEquals("1.2.3", v.toString()); Locale.setDefault(Locale.ROOT); } } @Nested class Serialization { @Test void shouldBeSerializable() throws Exception { Version v1 = Version.of(1, 2, 3, "alpha.1", "build.1"); Version v2 = deserialize(serialize(v1)); assertEquals(v1, v2); } private byte[] serialize(Version v) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(v); return baos.toByteArray(); } private Version deserialize(byte[] bytes) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return (Version) ois.readObject(); } } private static void assertThrowsIllegalArgumentException(Executable exec) { assertThrows(IllegalArgumentException.class, exec); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/000077500000000000000000000000001455073306400243645ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/AndTest.java000066400000000000000000000031241455073306400265710ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class AndTest { @Test void shouldCheckIfBothExpressionsEvaluateToTrue() { Expression left = version -> true; Expression right = version -> true; And and = new And(left, right); assertTrue(and.interpret(null)); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/CompositeExpressionTest.java000066400000000000000000000074441455073306400321220ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class CompositeExpressionTest { @Test void shouldSupportEqualExpression() { assertTrue(eq("1.0.0").interpret("1.0.0")); assertFalse(eq("1.0.0").interpret("2.0.0")); } @Test void shouldSupportNotEqualExpression() { assertTrue(neq("1.0.0").interpret("2.0.0")); } @Test void shouldSupportGreaterExpression() { assertTrue(gt("1.0.0").interpret("2.0.0")); assertFalse(gt("2.0.0").interpret("1.0.0")); } @Test 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 void shouldSupportLessExpression() { assertTrue(lt("2.0.0").interpret("1.0.0")); assertFalse(lt("1.0.0").interpret("2.0.0")); } @Test 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 void shouldSupportNotExpression() { assertTrue(not(eq("1.0.0")).interpret("2.0.0")); assertFalse(not(eq("1.0.0")).interpret("1.0.0")); } @Test 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 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 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")); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/EqualTest.java000066400000000000000000000031751455073306400271440ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class EqualTest { @Test void shouldCheckIfVersionIsEqualToParsedVersion() { Equal eq = new Equal(Version.of(1, 2, 3)); assertTrue(eq.interpret(Version.of(1, 2, 3))); assertFalse(eq.interpret(Version.of(3, 2, 1))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/ExpressionParserTest.java000066400000000000000000000263601455073306400314120ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 java.util.Arrays; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class ExpressionParserTest { @Test void shouldParseEqualComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression eq = parser.parse("=1.0.0"); assertTrue(eq.interpret(Version.of(1, 0, 0))); } @Test void shouldParseEqualComparisonRangeIfOnlyFullVersionGiven() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression eq = parser.parse("1.0.0"); assertTrue(eq.interpret(Version.of(1, 0, 0))); } @Test void shouldParseNotEqualComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression ne = parser.parse("!=1.0.0"); assertTrue(ne.interpret(Version.of(1, 2, 3))); } @Test void shouldParseGreaterComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression gt = parser.parse(">1.0.0"); assertTrue(gt.interpret(Version.of(1, 2, 3))); } @Test void shouldParseGreaterOrEqualComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression ge = parser.parse(">=1.0.0"); assertTrue(ge.interpret(Version.of(1, 0, 0))); assertTrue(ge.interpret(Version.of(1, 2, 3))); } @Test void shouldParseLessComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression lt = parser.parse("<1.2.3"); assertTrue(lt.interpret(Version.of(1, 0, 0))); } @Test void shouldParseLessOrEqualComparisonRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression le = parser.parse("<=1.2.3"); assertTrue(le.interpret(Version.of(1, 0, 0))); assertTrue(le.interpret(Version.of(1, 2, 3))); } @Test void shouldSupportLongNumericIdentifiersInComparisonRanges() { long l = Integer.MAX_VALUE + 1L; String version = "=" + l + "." + l + "." + l; ExpressionParser parser = new ExpressionParser(new Lexer()); Expression eq = parser.parse(version); assertTrue(eq.interpret(Version.of(l, l, l))); } @Test void shouldParseTildeRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr1 = parser.parse("~1"); assertTrue(expr1.interpret(Version.of(1, 2, 3))); assertFalse(expr1.interpret(Version.of(3, 2, 1))); Expression expr2 = parser.parse("~1.2"); assertTrue(expr2.interpret(Version.of(1, 2, 3))); assertFalse(expr2.interpret(Version.of(2, 0, 0))); Expression expr3 = parser.parse("~1.2.3"); assertTrue(expr3.interpret(Version.of(1, 2, 3))); assertFalse(expr3.interpret(Version.of(1, 3, 0))); } @Test void shouldSupportLongNumericIdentifiersInTildeRanges() { long l = Integer.MAX_VALUE + 1L; String tildeRange = "~" + l + "." + l + "." + l; ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse(tildeRange); assertTrue(expr.interpret(Version.of(l, l, l))); } @Test void shouldRaiseErrorIfIncrementCausesOverflowInTildeRanges() { long lmv = Long.MAX_VALUE; ExpressionParser parser = new ExpressionParser(new Lexer()); for (String r : Arrays.asList(("~" + lmv), ("~1." + lmv), ("~1." + lmv + ".0"))) { assertThrows(ArithmeticException.class, () -> parser.parse(r)); } } @Test void shouldParseCaretRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr1 = parser.parse("^1"); assertTrue(expr1.interpret(Version.of(1, 2, 3))); assertFalse(expr1.interpret(Version.of(3, 2, 1))); Expression expr2 = parser.parse("^0.2"); assertTrue(expr2.interpret(Version.of(0, 2, 3))); assertFalse(expr2.interpret(Version.of(0, 3, 0))); Expression expr3 = parser.parse("^0.0.3"); assertTrue(expr3.interpret(Version.of(0, 0, 3))); assertFalse(expr3.interpret(Version.of(0, 0, 4))); } @Test void shouldSupportLongNumericIdentifiersInCaretRanges() { long l = Integer.MAX_VALUE + 1L; String caretRange = "^" + l + "." + l + "." + l; ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse(caretRange); assertTrue(expr.interpret(Version.of(l, l, l))); } @Test void shouldRaiseErrorIfIncrementCausesOverflowInCaretRanges() { long lmv = Long.MAX_VALUE; ExpressionParser parser = new ExpressionParser(new Lexer()); for (String r : Arrays.asList(("^" + lmv), ("^0." + lmv), ("^0.0." + lmv))) { assertThrows(ArithmeticException.class, () -> parser.parse(r)); } } @Test void shouldParsePartialVersionRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr1 = parser.parse("1"); assertTrue(expr1.interpret(Version.of(1, 2, 3))); Expression expr2 = parser.parse("2.0"); assertTrue(expr2.interpret(Version.of(2, 0, 9))); } @Test void shouldSupportLongNumericIdentifiersInPartialVersionRanges() { long l = Integer.MAX_VALUE + 1L; String partialVersion = l + "." + l; ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse(partialVersion); assertTrue(expr.interpret(Version.of(l, l, l))); } @Test void shouldRaiseErrorIfIncrementCausesOverflowInPartialVersionRanges() { String lmv = String.valueOf(Long.MAX_VALUE); ExpressionParser parser = new ExpressionParser(new Lexer()); for (String r : Arrays.asList((lmv), ("1." + lmv))) { assertThrows(ArithmeticException.class, () -> parser.parse(r)); } } @Test void shouldParseWildcardRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr1 = parser.parse("1.*"); assertTrue(expr1.interpret(Version.of(1, 2, 3))); assertFalse(expr1.interpret(Version.of(3, 2, 1))); Expression expr2 = parser.parse("1.2.x"); assertTrue(expr2.interpret(Version.of(1, 2, 3))); assertFalse(expr2.interpret(Version.of(1, 3, 2))); Expression expr3 = parser.parse("X"); assertTrue(expr3.interpret(Version.of(1, 2, 3))); } @Test void shouldSupportLongNumericIdentifiersInWildcardRanges() { long l = Integer.MAX_VALUE + 1L; String wildcardRange = l + "." + l + "." + "x"; ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse(wildcardRange); assertTrue(expr.interpret(Version.of(l, l, l))); } @Test void shouldRaiseErrorIfIncrementCausesOverflowInWildcardRanges() { long lmv = Long.MAX_VALUE; ExpressionParser parser = new ExpressionParser(new Lexer()); for (String r : Arrays.asList((lmv + ".x"), ("1." + lmv + ".x"))) { assertThrows(ArithmeticException.class, () -> parser.parse(r)); } } @Test void shouldParseHyphenRange() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression range = parser.parse("1.0.0 - 2.0.0"); assertTrue(range.interpret(Version.of(1, 2, 3))); assertFalse(range.interpret(Version.of(3, 2, 1))); } @Test void shouldParseMultipleRangesJoinedWithAnd() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression and = parser.parse(">=1.0.0 && <2.0.0"); assertTrue(and.interpret(Version.of(1, 2, 3))); assertFalse(and.interpret(Version.of(3, 2, 1))); } @Test void shouldParseMultipleRangesJoinedWithOr() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression or = parser.parse("1.* || =2.0.0"); assertTrue(or.interpret(Version.of(1, 2, 3))); assertFalse(or.interpret(Version.of(2, 1, 0))); } @Test void shouldParseParenthesizedExpression() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse("(1)"); assertTrue(expr.interpret(Version.of(1, 2, 3))); assertFalse(expr.interpret(Version.of(2, 0, 0))); } @Test void shouldParseExpressionWithMultipleParentheses() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr = parser.parse("((1))"); assertTrue(expr.interpret(Version.of(1, 2, 3))); assertFalse(expr.interpret(Version.of(2, 0, 0))); } @Test void shouldParseNotExpression() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression not1 = parser.parse("!(1)"); assertTrue(not1.interpret(Version.of(2, 0, 0))); assertFalse(not1.interpret(Version.of(1, 2, 3))); Expression not2 = parser.parse("0.* & !(>=1 & <2)"); assertTrue(not2.interpret(Version.of(0, 5, 0))); assertFalse(not2.interpret(Version.of(1, 0, 1))); Expression not3 = parser.parse("!(>=1 & <2) & >=2"); assertTrue(not3.interpret(Version.of(2, 0, 0))); assertFalse(not3.interpret(Version.of(1, 2, 3))); } @Test void shouldRespectPrecedenceWhenUsedWithParentheses() { ExpressionParser parser = new ExpressionParser(new Lexer()); Expression expr1 = parser.parse("(~1.0 & <2.0) | >2.0"); assertTrue(expr1.interpret(Version.of(2, 5, 0))); Expression expr2 = parser.parse("~1.0 & (<2.0 | >2.0)"); assertFalse(expr2.interpret(Version.of(2, 5, 0))); } @Test 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.of(1, 5, 0))); assertFalse(expr.interpret(Version.of(2, 5, 0))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/GreaterOrEqualTest.java000066400000000000000000000033341455073306400307540ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class GreaterOrEqualTest { @Test void shouldCheckIfVersionIsGreaterThanOrEqualToParsedVersion() { GreaterOrEqual ge = new GreaterOrEqual(Version.of(2, 0, 0)); assertTrue(ge.interpret(Version.of(3, 2, 1))); assertTrue(ge.interpret(Version.of(2, 0, 0))); assertFalse(ge.interpret(Version.of(1, 2, 3))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/GreaterTest.java000066400000000000000000000032071455073306400274620ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class GreaterTest { @Test void shouldCheckIfVersionIsGreaterThanParsedVersion() { Greater gt = new Greater(Version.of(2, 0, 0)); assertTrue(gt.interpret(Version.of(3, 2, 1))); assertFalse(gt.interpret(Version.of(1, 2, 3))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/LessOrEqualTest.java000066400000000000000000000033201455073306400302640ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class LessOrEqualTest { @Test void shouldCheckIfVersionIsLessThanOrEqualToParsedVersion() { LessOrEqual le = new LessOrEqual(Version.of(2, 0, 0)); assertTrue(le.interpret(Version.of(1, 2, 3))); assertTrue(le.interpret(Version.of(2, 0, 0))); assertFalse(le.interpret(Version.of(3, 2, 1))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/LessTest.java000066400000000000000000000031731455073306400270010ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class LessTest { @Test void shouldCheckIfVersionIsLessThanParsedVersion() { Less lt = new Less(Version.of(2, 0, 0)); assertTrue(lt.interpret(Version.of(1, 2, 3))); assertFalse(lt.interpret(Version.of(3, 2, 1))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/LexerTest.java000066400000000000000000000062511455073306400271520ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.expr.Lexer.*; import com.github.zafarkhaja.semver.util.Stream; import org.junit.jupiter.api.Test; import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class LexerTest { @Test void shouldTokenizeVersionString() { Token[] expected = { new Token(GREATER, ">", 0), new Token(NUMERIC, "1", 1), new Token(DOT, ".", 2), new Token(NUMERIC, "0", 3), new Token(DOT, ".", 4), new Token(NUMERIC, "0", 5), new Token(EOI, null, 6), }; Lexer lexer = new Lexer(); Stream stream = lexer.tokenize(">1.0.0"); assertArrayEquals(expected, stream.toArray()); } @Test void shouldSkipWhitespaces() { Token[] expected = { new Token(GREATER, ">", 0), new Token(NUMERIC, "1", 2), new Token(EOI, null, 3), }; Lexer lexer = new Lexer(); Stream stream = lexer.tokenize("> 1"); assertArrayEquals(expected, stream.toArray()); } @Test void shouldEndWithEol() { Token[] expected = { new Token(NUMERIC, "1", 0), new Token(DOT, ".", 1), new Token(NUMERIC, "2", 2), new Token(DOT, ".", 3), new Token(NUMERIC, "3", 4), new Token(EOI, null, 5), }; Lexer lexer = new Lexer(); Stream stream = lexer.tokenize("1.2.3"); assertArrayEquals(expected, stream.toArray()); } @Test void shouldRaiseErrorOnIllegalCharacter() { Lexer lexer = new Lexer(); LexerException e = assertThrows( LexerException.class, () -> lexer.tokenize("@1.0.0"), "Should raise error on illegal character" ); assertNotNull(e.getMessage()); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/LexerTokenTest.java000066400000000000000000000076101455073306400301530ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.expr.Lexer.Token; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class LexerTokenTest { @Nested class EqualsMethod { @Test void shouldBeReflexive() { Token token = new Token(NUMERIC, "1", 0); assertTrue(token.equals(token)); } @Test void shouldBeSymmetric() { Token t1 = new Token(EQUAL, "=", 0); Token t2 = new Token(EQUAL, "=", 0); assertTrue(t1.equals(t2)); assertTrue(t2.equals(t1)); } @Test void shouldBeTransitive() { Token t1 = new Token(GREATER, ">", 0); Token t2 = new Token(GREATER, ">", 0); Token t3 = new Token(GREATER, ">", 0); assertTrue(t1.equals(t2)); assertTrue(t2.equals(t3)); assertTrue(t1.equals(t3)); } @Test void shouldBeConsistent() { Token t1 = new Token(HYPHEN, "-", 0); Token t2 = new Token(HYPHEN, "-", 0); assertTrue(t1.equals(t2)); assertTrue(t1.equals(t2)); assertTrue(t1.equals(t2)); } @Test void shouldReturnFalseIfOtherVersionIsOfDifferentType() { Token t1 = new Token(DOT, ".", 0); assertFalse(t1.equals(new String("."))); } @Test void shouldReturnFalseIfOtherVersionIsNull() { Token t1 = new Token(AND, "&", 0); Token t2 = null; assertFalse(t1.equals(t2)); } @Test void shouldReturnFalseIfTypesAreDifferent() { Token t1 = new Token(EQUAL, "=", 0); Token t2 = new Token(NOT_EQUAL, "!=", 0); assertFalse(t1.equals(t2)); } @Test void shouldReturnFalseIfLexemesAreDifferent() { Token t1 = new Token(NUMERIC, "1", 0); Token t2 = new Token(NUMERIC, "2", 0); assertFalse(t1.equals(t2)); } @Test void shouldReturnFalseIfPositionsAreDifferent() { Token t1 = new Token(NUMERIC, "1", 1); Token t2 = new Token(NUMERIC, "1", 2); assertFalse(t1.equals(t2)); } } @Nested class HashCodeMethod { @Test void shouldReturnSameHashCodeIfTokensAreEqual() { Token t1 = new Token(NUMERIC, "1", 0); Token t2 = new Token(NUMERIC, "1", 0); assertTrue(t1.equals(t2)); assertEquals(t1.hashCode(), t2.hashCode()); } } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/NotEqualTest.java000066400000000000000000000032111455073306400276140ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class NotEqualTest { @Test void shouldCheckIfVersionIsNotEqualToParsedVersion() { NotEqual ne = new NotEqual(Version.of(1, 2, 3)); assertTrue(ne.interpret(Version.of(3, 2, 1))); assertFalse(ne.interpret(Version.of(1, 2, 3))); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/NotTest.java000066400000000000000000000032401455073306400266260ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class NotTest { @Test void shouldRevertBooleanResultOfExpression() { Expression expr1 = version -> false; Expression expr2 = version -> true; Not not; not = new Not(expr1); assertTrue(not.interpret(null)); not = new Not(expr2); assertFalse(not.interpret(null)); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/OrTest.java000066400000000000000000000031241455073306400264470ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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 org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class OrTest { @Test void shouldCheckIfOneOfTwoExpressionsEvaluateToTrue() { Expression left = version -> false; Expression right = version -> true; Or or = new Or(left, right); assertTrue(or.interpret(null)); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/expr/ParserErrorHandlingTest.java000066400000000000000000000054541455073306400320120ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.expr.Lexer.Token; import java.util.Arrays; import java.util.Collection; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class ParserErrorHandlingTest { @ParameterizedTest @MethodSource("parameters") void shouldCorrectlyHandleParseErrors( String invalidExpr, Token unexpected, Token.Type[] expected ) { UnexpectedTokenException e = assertThrows( UnexpectedTokenException.class, () -> ExpressionParser.newInstance().parse(invalidExpr) ); assertNotNull(e.getMessage()); assertEquals(unexpected, e.getUnexpectedToken()); assertArrayEquals(expected, e.getExpectedTokenTypes()); } static Collection parameters() { return Arrays.asList(new Object[][] { { "1)", new Token(RIGHT_PAREN, ")", 1), new Token.Type[] { EOI } }, { "(>1.0.1", new Token(EOI, null, 7), new Token.Type[] { RIGHT_PAREN } }, { "((>=1 & <2)", new Token(EOI, null, 11), new Token.Type[] { RIGHT_PAREN } }, { ">=1.0.0 &", new Token(EOI, null, 9), new Token.Type[] { NUMERIC } }, { "(>2.0 |)", new Token(RIGHT_PAREN, ")", 7), new Token.Type[] { NUMERIC } }, { "& 1.2", new Token(AND, "&", 0), new Token.Type[] { NUMERIC } }, }); } } jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/util/000077500000000000000000000000001455073306400243635ustar00rootroot00000000000000jsemver-0.10.2/src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java000066400000000000000000000142461455073306400273300ustar00rootroot00000000000000/* * The MIT License * * Copyright 2012-2024 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.util; import java.util.Iterator; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /** * * @author Zafar Khaja {@literal } */ class StreamTest { @Test void shouldBeBackedByArray() { Character[] input = {'a', 'b', 'c'}; Stream stream = new Stream<>(input); assertArrayEquals(input, stream.toArray()); } @Test void shouldImplementIterable() { Character[] input = {'a', 'b', 'c'}; Stream stream = new Stream<>(input); Iterator it = stream.iterator(); for (Character chr : input) { assertEquals(chr, it.next()); } assertFalse(it.hasNext()); } @Test void shouldNotReturnRealElementsArray() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); Character[] charArray = stream.toArray(); charArray[0] = 'z'; assertEquals(Character.valueOf('z'), charArray[0]); assertEquals(Character.valueOf('a'), stream.toArray()[0]); } @Test void shouldReturnArrayOfElementsThatAreLeftInStream() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); stream.consume(); stream.consume(); assertEquals(1, stream.toArray().length); assertEquals(Character.valueOf('c'), stream.toArray()[0]); } @Test void shouldConsumeElementsOneByOne() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(Character.valueOf('a'), stream.consume()); assertEquals(Character.valueOf('b'), stream.consume()); assertEquals(Character.valueOf('c'), stream.consume()); } @Test void shouldRaiseErrorWhenUnexpectedElementConsumed() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); UnexpectedElementException e = assertThrows( UnexpectedElementException.class, () -> stream.consume(element -> false), "Should raise error when unexpected element type is consumed" ); assertNotNull(e.getMessage()); } @Test void shouldLookaheadWithoutConsuming() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(Character.valueOf('a'), stream.lookahead()); assertEquals(Character.valueOf('a'), stream.lookahead()); } @Test void shouldLookaheadArbitraryNumberOfElements() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(Character.valueOf('a'), stream.lookahead(1)); assertEquals(Character.valueOf('b'), stream.lookahead(2)); assertEquals(Character.valueOf('c'), stream.lookahead(3)); } @Test void shouldCheckIfLookaheadIsOfExpectedTypes() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertTrue(stream.positiveLookahead(element -> element == 'a')); assertFalse(stream.positiveLookahead(element -> element == 'c')); } @Test void shouldCheckIfElementOfExpectedTypesExistBeforeGivenType() { Stream stream = new Stream<>(new Character[] {'1', '.', '0', '.', '0'}); assertTrue(stream.positiveLookaheadBefore( element -> element == '.', element -> element == '1' )); assertFalse(stream.positiveLookaheadBefore( element -> element == '1', element -> element == '.' )); } @Test void shouldCheckIfElementOfExpectedTypesExistUntilGivenPosition() { Stream stream = new Stream<>(new Character[] {'1', '.', '0', '.', '0'}); assertTrue(stream.positiveLookaheadUntil(3, element -> element == '0')); assertFalse(stream.positiveLookaheadUntil(3, element -> element == 'a')); } @Test void shouldPushBackOneElementAtATime() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(Character.valueOf('a'), stream.consume()); stream.pushBack(); assertEquals(Character.valueOf('a'), stream.consume()); } @Test void shouldStopPushingBackWhenThereAreNoElements() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(Character.valueOf('a'), stream.consume()); assertEquals(Character.valueOf('b'), stream.consume()); assertEquals(Character.valueOf('c'), stream.consume()); stream.pushBack(); stream.pushBack(); stream.pushBack(); stream.pushBack(); assertEquals(Character.valueOf('a'), stream.consume()); } @Test void shouldKeepTrackOfCurrentOffset() { Stream stream = new Stream<>(new Character[] {'a', 'b', 'c'}); assertEquals(0, stream.currentOffset()); stream.consume(); assertEquals(1, stream.currentOffset()); stream.consume(); stream.consume(); assertEquals(3, stream.currentOffset()); } }