Add Javadoc to the source code

This commit is contained in:
Zafar Khaja 2013-11-13 21:40:09 +04:00
parent 388f94915f
commit cf8d6b1960
27 changed files with 1376 additions and 5 deletions

View File

@ -218,11 +218,6 @@ other interesting capabilities of the SemVer Expressions DSL.
* Parenthesized expression - `~1.3 | (1.4.* & !=1.4.5) | ~2`
TODO
----
* [Write doc comments for all API classes and methods](https://github.com/zafarkhaja/java-semver/issues/2)
Bugs and Features
-----------------
Bug reports and feature requests can be submitted at https://github.com/zafarkhaja/java-semver/issues.

View File

@ -24,11 +24,19 @@
package com.github.zafarkhaja.semver;
/**
* Thrown when an error occurs during the parsing specified
* by the SemVer or the formal grammar of the parsed string.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public class GrammarException extends ParserException {
/**
* Constructs a {@code GrammarException} instance with an error message.
*
* @param message the error message
*/
GrammarException(String message) {
super(message);
}

View File

@ -26,39 +26,66 @@ package com.github.zafarkhaja.semver;
import java.util.Arrays;
/**
* The {@code MetadataVersion} class is used to represent
* the pre-release version and the build metadata.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.2.0
*/
class MetadataVersion implements Comparable<MetadataVersion> {
/**
* Null metadata, the implementation of the Null Object design pattern.
*/
static final MetadataVersion NULL = new NullMetadataVersion();
/**
* The implementation of the Null Object design pattern.
*/
private static class NullMetadataVersion extends MetadataVersion {
/**
* Constructs a {@code NullMetadataVersion} instance.
*/
public NullMetadataVersion() {
super(null);
}
/**
* @throws NullPointerException as Null metadata cannot be incremented
*/
@Override
MetadataVersion increment() {
throw new NullPointerException("Metadata version is NULL");
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "";
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
return other instanceof NullMetadataVersion;
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(MetadataVersion other) {
if (!equals(other)) {
@ -72,12 +99,24 @@ class MetadataVersion implements Comparable<MetadataVersion> {
}
}
/**
* The array containing the version's identifiers.
*/
private final String[] idents;
/**
* Constructs a {@code MetadataVersion} instance with identifiers.
* @param identifiers the version's identifiers
*/
MetadataVersion(String[] identifiers) {
idents = identifiers;
}
/**
* Increments the metadata version.
*
* @return a new instance of the {@code MetadataVersion} class
*/
MetadataVersion increment() {
String[] ids = idents;
String lastId = ids[ids.length - 1];
@ -91,6 +130,9 @@ class MetadataVersion implements Comparable<MetadataVersion> {
return new MetadataVersion(ids);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
if (this == other) {
@ -102,11 +144,17 @@ class MetadataVersion implements Comparable<MetadataVersion> {
return compareTo((MetadataVersion) other) == 0;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Arrays.hashCode(idents);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -116,6 +164,9 @@ class MetadataVersion implements Comparable<MetadataVersion> {
return sb.deleteCharAt(sb.lastIndexOf(".")).toString();
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(MetadataVersion other) {
if (other == MetadataVersion.NULL) {
@ -127,11 +178,23 @@ class MetadataVersion implements Comparable<MetadataVersion> {
}
int result = compareIdentifierArrays(other.idents);
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 = idents.length - other.idents.length;
}
return result;
}
/**
* Compares two arrays of identifiers.
*
* @param otherIdents the identifiers of the other version
* @return integer result of comparison compatible with
* the {@code Comparable.compareTo} method
*/
private int compareIdentifierArrays(String[] otherIdents) {
int result = 0;
int length = getLeastCommonArrayLength(idents, otherIdents);
@ -144,10 +207,25 @@ class MetadataVersion implements Comparable<MetadataVersion> {
return result;
}
/**
* Returns the size of the smallest array.
*
* @param arr1 the first array
* @param arr2 the second array
* @return the size of the smallest array
*/
private int getLeastCommonArrayLength(String[] arr1, String[] arr2) {
return arr1.length <= arr2.length ? arr1.length : arr2.length;
}
/**
* Compares two identifiers.
*
* @param ident1 the first identifier
* @param ident2 the second identifier
* @return integer result of comparison compatible with
* the {@code Comparable.compareTo} method
*/
private int compareIdentifiers(String ident1, String ident2) {
if (isInt(ident1) && isInt(ident2)) {
return Integer.parseInt(ident1) - Integer.parseInt(ident2);
@ -156,6 +234,13 @@ class MetadataVersion implements Comparable<MetadataVersion> {
}
}
/**
* Checks if the specified string is an integer.
*
* @param str the string to check
* @return {@code true} if the specified string is an integer
* or {@code false} otherwise
*/
private boolean isInt(String str) {
try {
Integer.parseInt(str);

View File

@ -24,15 +24,39 @@
package com.github.zafarkhaja.semver;
/**
* The {@code NormalVersion} class represents the version core.
*
* This class is immutable and hence thread-safe.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.2.0
*/
class NormalVersion implements Comparable<NormalVersion> {
/**
* The major version number.
*/
private final int major;
/**
* The minor version number.
*/
private final int minor;
/**
* The patch version number.
*/
private final int patch;
/**
* Constructs a {@code NormalVersion} with the
* major, minor and patch version numbers.
*
* @param major the major version number
* @param minor the minor version number
* @param patch the patch version number
* @throws IllegalArgumentException if one of the version numbers is a negative integer
*/
NormalVersion(int major, int minor, int patch) {
if (major < 0 || minor < 0 || patch < 0) {
throw new IllegalArgumentException(
@ -44,30 +68,63 @@ class NormalVersion implements Comparable<NormalVersion> {
this.patch = patch;
}
/**
* Returns the major version number.
*
* @return the major version number
*/
int getMajor() {
return major;
}
/**
* Returns the minor version number.
*
* @return the minor version number
*/
int getMinor() {
return minor;
}
/**
* Returns the patch version number.
*
* @return the patch version number
*/
int getPatch() {
return patch;
}
/**
* Increments the major version number.
*
* @return a new instance of the {@code NormalVersion} class
*/
NormalVersion incrementMajor() {
return new NormalVersion(major + 1, 0, 0);
}
/**
* Increments the minor version number.
*
* @return a new instance of the {@code NormalVersion} class
*/
NormalVersion incrementMinor() {
return new NormalVersion(major, minor + 1, 0);
}
/**
* Increments the patch version number.
*
* @return a new instance of the {@code NormalVersion} class
*/
NormalVersion incrementPatch() {
return new NormalVersion(major, minor, patch + 1);
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(NormalVersion other) {
int result = major - other.major;
@ -80,6 +137,9 @@ class NormalVersion implements Comparable<NormalVersion> {
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
if (this == other) {
@ -91,6 +151,9 @@ class NormalVersion implements Comparable<NormalVersion> {
return compareTo((NormalVersion) other) == 0;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = 17;
@ -106,6 +169,8 @@ class NormalVersion implements Comparable<NormalVersion> {
* A normal version number MUST take the form X.Y.Z where X, Y, and Z are
* non-negative integers. X is the major version, Y is the minor version,
* and Z is the patch version. (SemVer p.2)
*
* @return the string representation of this normal version
*/
@Override
public String toString() {

View File

@ -24,9 +24,20 @@
package com.github.zafarkhaja.semver;
/**
* A parser interface.
*
* @param <T> the type of parser's output
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public interface Parser<T> {
/**
* Parses the input string.
*
* @param input the string to parse
* @return the Abstract Syntax Tree
*/
T parse(String input);
}

View File

@ -24,15 +24,25 @@
package com.github.zafarkhaja.semver;
/**
* Thrown to indicate an error during the parsing.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public class ParserException extends RuntimeException {
/**
* Constructs a {@code ParserException} instance with an error message.
*
* @param message the error message
*/
public ParserException(String message) {
super(message);
}
/**
* Constructs a {@code ParserException} instance with no error message.
*/
public ParserException() {
}

View File

@ -28,24 +28,70 @@ import com.github.zafarkhaja.semver.expr.ExpressionParser;
import java.util.Comparator;
/**
* The {@code Version} class is the main class of the Java SemVer library.
*
* This class implements the Facade design pattern.
* It is also immutable, which makes the class thread-safe.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.1.0
*/
public class Version implements Comparable<Version> {
/**
* The normal version.
*/
private final NormalVersion normal;
/**
* The pre-release version.
*/
private final MetadataVersion preRelease;
/**
* The build metadata.
*/
private final MetadataVersion build;
/**
* A separator that separates the pre-release
* version from the normal version.
*/
private static final String PRE_RELEASE_PREFIX = "-";
/**
* A separator that separates the build metadata from
* the normal version or the pre-release version.
*/
private static final String BUILD_PREFIX = "+";
/**
* A mutable builder for the immutable {@code Version} class.
*/
public static class Builder {
/**
* The normal version string.
*/
private String normal;
/**
* The pre-release version string.
*/
private String preRelease;
/**
* The build metadata string.
*/
private String build;
/**
* Constructs a {@code Builder} instance with the
* string representation of the normal version.
*
* @param normal the string representation of the normal version
* @throws NullPointerException if the specified normal version is null
*/
public Builder(String normal) {
if (normal == null) {
throw new NullPointerException(
@ -55,14 +101,29 @@ public class Version implements Comparable<Version> {
this.normal = normal;
}
/**
* Sets the pre-release version.
*
* @param preRelease the string representation of the pre-release version
*/
public void setPreReleaseVersion(String preRelease) {
this.preRelease = preRelease;
}
/**
* Sets the build metadata.
*
* @param build the string representation of the build metadata
*/
public void setBuildMetadata(String build) {
this.build = build;
}
/**
* Builds a {@code Version} object.
*
* @return a newly built {@code Version} instance
*/
public Version build() {
return new Version(
VersionParser.parseVersionCore(normal),
@ -72,10 +133,30 @@ public class Version implements Comparable<Version> {
}
}
/**
* A comparator that respects the build metadata when comparing versions.
*/
public static final Comparator BUILD_AWARE_ORDER = new BuildAwareOrder();
/**
* A build-aware comparator.
*/
private static class BuildAwareOrder implements Comparator<Version> {
/**
* Compares two {@code Version} instances taking
* into account their build metadata.
*
* When compared build metadata is divided into identifiers. The
* numeric identifiers are compared numerically, and the alphanumeric
* identifiers are compared in the ASCII sort order.
*
* If one of the compared versions has no defined build
* metadata, this version is considered to have a lower
* precedence than that of the other.
*
* @return {@inheritDoc}
*/
@Override
public int compare(Version v1, Version v2) {
int result = v1.compareTo(v2);
@ -96,14 +177,34 @@ public class Version implements Comparable<Version> {
}
}
/**
* Constructs a {@code Version} instance with the normal version.
*
* @param normal the normal version
*/
Version(NormalVersion normal) {
this(normal, MetadataVersion.NULL, MetadataVersion.NULL);
}
/**
* Constructs a {@code Version} instance with the
* normal version and the pre-release version.
*
* @param normal the normal version
* @param preRelease the pre-release version
*/
Version(NormalVersion normal, MetadataVersion preRelease) {
this(normal, preRelease, MetadataVersion.NULL);
}
/**
* Constructs a {@code Version} instance with the normal
* version, the pre-release version and the build metadata.
*
* @param normal the normal version
* @param preRelease the pre-release version
* @param build the build metadata
*/
Version(
NormalVersion normal,
MetadataVersion preRelease,
@ -114,31 +215,86 @@ public class Version implements Comparable<Version> {
this.build = build;
}
/**
* Creates a new instance of {@code Version} as a
* result of parsing the specified version string.
*
* @param version the version string to parse
* @return a new instance of the {@code Version} class
*/
public static Version valueOf(String version) {
return VersionParser.parseValidSemVer(version);
}
/**
* Creates a new instance of {@code Version}
* for the specified version numbers.
*
* @param major the major version number
* @return a new instance of the {@code Version} class
* @since 0.7.0
*/
public static Version forIntegers(int major) {
return new Version(new NormalVersion(major, 0, 0));
}
/**
* Creates a new instance of {@code Version}
* for the specified version numbers.
*
* @param major the major version number
* @param minor the minor version number
* @return a new instance of the {@code Version} class
* @since 0.7.0
*/
public static Version forIntegers(int major, int minor) {
return new Version(new NormalVersion(major, minor, 0));
}
/**
* Creates a new instance of {@code Version}
* for the specified version numbers.
*
* @param major the major version number
* @param minor the minor version number
* @param patch the patch version number
* @return a new instance of the {@code Version} class
* @since 0.7.0
*/
public static Version forIntegers(int major, int minor, int patch) {
return new Version(new NormalVersion(major, minor, patch));
}
/**
* Checks if this version satisfies the specified SemVer Expression.
*
* This method is a part of the SemVer Expressions API.
*
* @param expr the SemVer Expression
* @return {@code true} if this version satisfies the specified
* SemVer Expression or {@code false} otherwise
* @since 0.7.0
*/
public boolean satisfies(String expr) {
Parser<Expression> parser = ExpressionParser.newInstance();
return parser.parse(expr).interpret(this);
}
/**
* Increments the major version.
*
* @return a new instance of the {@code Version} class
*/
public Version incrementMajorVersion() {
return new Version(normal.incrementMajor());
}
/**
* Increments the major version and appends the pre-release version.
*
* @param preRelease the pre-release version to append
* @return a new instance of the {@code Version} class
*/
public Version incrementMajorVersion(String preRelease) {
return new Version(
normal.incrementMajor(),
@ -146,10 +302,21 @@ public class Version implements Comparable<Version> {
);
}
/**
* Increments the minor version.
*
* @return a new instance of the {@code Version} class
*/
public Version incrementMinorVersion() {
return new Version(normal.incrementMinor());
}
/**
* Increments the minor version and appends the pre-release version.
*
* @param preRelease the pre-release version to append
* @return a new instance of the {@code Version} class
*/
public Version incrementMinorVersion(String preRelease) {
return new Version(
normal.incrementMinor(),
@ -157,10 +324,21 @@ public class Version implements Comparable<Version> {
);
}
/**
* Increments the patch version.
*
* @return a new instance of the {@code Version} class
*/
public Version incrementPatchVersion() {
return new Version(normal.incrementPatch());
}
/**
* Increments the patch version and appends the pre-release version.
*
* @param preRelease the pre-release version to append
* @return a new instance of the {@code Version} class
*/
public Version incrementPatchVersion(String preRelease) {
return new Version(
normal.incrementPatch(),
@ -168,14 +346,30 @@ public class Version implements Comparable<Version> {
);
}
/**
* Increments the pre-release version.
*
* @return a new instance of the {@code Version} class
*/
public Version incrementPreReleaseVersion() {
return new Version(normal, preRelease.increment());
}
/**
* Increments the build metadata.
*
* @return a new instance of the {@code Version} class
*/
public Version incrementBuildMetadata() {
return new Version(normal, preRelease, build.increment());
}
/**
* Sets the pre-release version.
*
* @param preRelease the pre-release version to set
* @return a new instance of the {@code Version} class
*/
public Version setPreReleaseVersion(String preRelease) {
return new Version(
normal,
@ -183,6 +377,12 @@ public class Version implements Comparable<Version> {
);
}
/**
* Sets the build metadata.
*
* @param build the build metadata to set
* @return a new instance of the {@code Version} class
*/
public Version setBuildMetadata(String build) {
return new Version(
normal,
@ -191,46 +391,118 @@ public class Version implements Comparable<Version> {
);
}
/**
* Returns the major version number.
*
* @return the major version number
*/
public int getMajorVersion() {
return normal.getMajor();
}
/**
* Returns the minor version number.
*
* @return the minor version number
*/
public int getMinorVersion() {
return normal.getMinor();
}
/**
* Returns the patch version number.
*
* @return the patch version number
*/
public int getPatchVersion() {
return normal.getPatch();
}
/**
* Returns the string representation of the normal version.
*
* @return the string representation of the normal version
*/
public String getNormalVersion() {
return normal.toString();
}
/**
* Returns the string representation of the pre-release version.
*
* @return the string representation of the pre-release version
*/
public String getPreReleaseVersion() {
return preRelease.toString();
}
/**
* Returns the string representation of the build metadata.
*
* @return the string representation of the build metadata
*/
public String getBuildMetadata() {
return build.toString();
}
/**
* Checks if this version is greater than the other version.
*
* @param other the other version to compare to
* @return {@code true} if this version is greater than the other version
* or {@code false} otherwise
* @see #compareTo(Version other)
*/
public boolean greaterThan(Version other) {
return compareTo(other) > 0;
}
/**
* Checks if this version is greater than or equal to the other version.
*
* @param other the other version to compare to
* @return {@code true} if this version is greater than or equal
* to the other version or {@code false} otherwise
* @see #compareTo(Version other)
*/
public boolean greaterThanOrEqualTo(Version other) {
return compareTo(other) >= 0;
}
/**
* Checks if this version is less than the other version.
*
* @param other the other version to compare to
* @return {@code true} if this version is less than the other version
* or {@code false} otherwise
* @see #compareTo(Version other)
*/
public boolean lessThan(Version other) {
return compareTo(other) < 0;
}
/**
* Checks if this version is less than or equal to the other version.
*
* @param other the other version to compare to
* @return {@code true} if this version is less than or equal
* to the other version or {@code false} otherwise
* @see #compareTo(Version other)
*/
public boolean lessThanOrEqualTo(Version other) {
return compareTo(other) <= 0;
}
/**
* Checks if this version equals the other version.
*
* The comparison is done by the {@code Version.compareTo} method.
*
* @param other the other version to compare to
* @return {@code true} if this version equals the other version
* or {@code false} otherwise
* @see #compareTo(Version other)
*/
@Override
public boolean equals(Object other) {
if (this == other) {
@ -242,6 +514,9 @@ public class Version implements Comparable<Version> {
return compareTo((Version) other) == 0;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = 5;
@ -251,6 +526,9 @@ public class Version implements Comparable<Version> {
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getNormalVersion());
@ -263,6 +541,20 @@ public class Version implements Comparable<Version> {
return sb.toString();
}
/**
* Compares this version to the other version.
*
* This method does not take into account the versions' build
* metadata. If you want to compare the versions' build metadata
* use the {@code Version.compareWithBuildsTo} method or the
* {@code Version.BUILD_AWARE_ORDER} comparator.
*
* @param other the other version to compare to
* @return a negative integer, zero or a positive integer if this version
* is less than, equal to or greater the the specified version
* @see #BUILD_AWARE_ORDER
* @see #compareWithBuildsTo(Version other)
*/
@Override
public int compareTo(Version other) {
int result = normal.compareTo(other.normal);
@ -272,6 +564,17 @@ public class Version implements Comparable<Version> {
return result;
}
/**
* Compare this version to the other version
* taking into account the build metadata.
*
* The method makes use of the {@code Version.BUILD_AWARE_ORDER} comparator.
*
* @param other the other version to compare to
* @return integer result of comparison compatible with
* that of the {@code Comparable.compareTo} method
* @see #BUILD_AWARE_ORDER
*/
public int compareWithBuildsTo(Version other) {
return BUILD_AWARE_ORDER.compare(this, other);
}

View File

@ -27,16 +27,25 @@ import com.github.zafarkhaja.semver.util.Stream;
import java.util.ArrayList;
import java.util.List;
import static com.github.zafarkhaja.semver.VersionParser.Char.*;
import com.github.zafarkhaja.semver.util.UnexpectedElementTypeException;
/**
* A parser for the SemVer Version.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
class VersionParser implements Parser<Version> {
/**
* Valid character types.
*/
static enum Char implements Stream.ElementType<Character> {
DIGIT {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
if (chr == null) {
@ -46,6 +55,9 @@ class VersionParser implements Parser<Version> {
}
},
LETTER {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
if (chr == null) {
@ -56,6 +68,9 @@ class VersionParser implements Parser<Version> {
}
},
DOT {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
if (chr == null) {
@ -65,6 +80,9 @@ class VersionParser implements Parser<Version> {
}
},
HYPHEN {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
if (chr == null) {
@ -74,6 +92,9 @@ class VersionParser implements Parser<Version> {
}
},
PLUS {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
if (chr == null) {
@ -83,6 +104,9 @@ class VersionParser implements Parser<Version> {
}
},
EOL {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Character chr) {
return chr == null;
@ -90,8 +114,17 @@ class VersionParser implements Parser<Version> {
};
}
/**
* The stream of characters.
*/
private final Stream<Character> chars;
/**
* Constructs a {@code VersionParser} instance
* with the input string to parse.
*
* @param input the input string to parse
*/
VersionParser(String input) {
Character[] elements = new Character[input.length()];
for (int i = 0; i < input.length(); i++) {
@ -100,21 +133,59 @@ class VersionParser implements Parser<Version> {
chars = new Stream<Character>(elements);
}
/**
* Parses the input string.
*
* @param input the input string to parse
* @return a valid version object
* @throws GrammarException when there is an error defined in
* the SemVer or the formal grammar
* @throws UnexpectedElementTypeException 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 GrammarException when there is an error defined in
* the SemVer or the formal grammar
* @throws UnexpectedElementTypeException when encounters an unexpected
* character type
*/
static Version parseValidSemVer(String version) {
VersionParser parser = new VersionParser(version);
return parser.parseValidSemVer();
}
/**
* Parses the version core.
*
* @param versionCore the version core string to parse
* @return a valid normal version object
* @throws GrammarException when there is an error defined in
* the SemVer or the formal grammar
* @throws UnexpectedElementTypeException when encounters an unexpected
* character type
*/
static NormalVersion parseVersionCore(String versionCore) {
VersionParser parser = new VersionParser(versionCore);
return parser.parseVersionCore();
}
/**
* Parses the pre-release version.
*
* @param preRelease the pre-release version string to parse
* @return a valid pre-release version object
* @throws GrammarException when there is an error defined in
* the SemVer or the formal grammar
*/
static MetadataVersion parsePreRelease(String preRelease) {
if (preRelease == null) {
return MetadataVersion.NULL;
@ -123,6 +194,14 @@ class VersionParser implements Parser<Version> {
return parser.parsePreRelease();
}
/**
* Parses the build metadata.
*
* @param build the build metadata string to parse
* @return a valid build metadata object
* @throws GrammarException when there is an error defined in
* the SemVer or the formal grammar
*/
static MetadataVersion parseBuild(String build) {
if (build == null) {
return MetadataVersion.NULL;
@ -131,6 +210,20 @@ class VersionParser implements Parser<Version> {
return parser.parseBuild();
}
/**
* Parses the {@literal <valid semver>} non-terminal.
*
* <pre>
* {@literal
* <valid semver> ::= <version core>
* | <version core> "-" <pre-release>
* | <version core> "+" <build>
* | <version core> "-" <pre-release> "+" <build>
* }
* </pre>
*
* @return a valid version object
*/
private Version parseValidSemVer() {
NormalVersion normalVersion = parseVersionCore();
MetadataVersion preReleaseVersion = MetadataVersion.NULL;
@ -150,6 +243,17 @@ class VersionParser implements Parser<Version> {
);
}
/**
* Parses the {@literal <version core>} non-terminal.
*
* <pre>
* {@literal
* <version core> ::= <major> "." <minor> "." <patch>
* }
* </pre>
*
* @return a valid normal version object
*/
private NormalVersion parseVersionCore() {
int major = Integer.parseInt(numericIdentifier());
chars.consume(DOT);
@ -159,6 +263,24 @@ class VersionParser implements Parser<Version> {
return new NormalVersion(major, minor, patch);
}
/**
* Parses the {@literal <pre-release>} non-terminal.
*
* <pre>
* {@literal
* <pre-release> ::= <dot-separated pre-release identifiers>
*
* <dot-separated pre-release identifiers> ::= <pre-release identifier>
* | <pre-release identifier> "." <dot-separated pre-release identifiers>
*
* <pre-release identifier> ::= <alphanumeric identifier>
* | <numeric identifier>
* }
* </pre>
*
* @return a valid pre-release version object
* @throws GrammarException if the pre-release version has empty identifier(s)
*/
private MetadataVersion parsePreRelease() {
Char end = closestEndpoint(PLUS, EOL);
Char before = closestEndpoint(DOT, end);
@ -182,6 +304,24 @@ class VersionParser implements Parser<Version> {
);
}
/**
* Parses the {@literal <build>} non-terminal.
*
* <pre>
* {@literal
* <build> ::= <dot-separated build identifiers>
*
* <dot-separated build identifiers> ::= <build identifier>
* | <build identifier> "." <dot-separated build identifiers>
*
* <build identifier> ::= <alphanumeric identifier>
* | <digits>
* }
* </pre>
*
* @return a valid build metadata object
* @throws GrammarException if the build metadata has empty identifier(s)
*/
private MetadataVersion parseBuild() {
Char end = EOL;
Char before = closestEndpoint(DOT, end);
@ -205,6 +345,20 @@ class VersionParser implements Parser<Version> {
);
}
/**
* Parses the {@literal <numeric identifier>} non-terminal.
*
* <pre>
* {@literal
* <numeric identifier> ::= "0"
* | <positive digit>
* | <positive digit> <digits>
* }
* </pre>
*
* @return a string representing the numeric identifier
* @throws GrammarException if the numeric identifier has leading zero(es)
*/
private String numericIdentifier() {
checkForLeadingZeroes();
StringBuilder sb = new StringBuilder();
@ -215,6 +369,20 @@ class VersionParser implements Parser<Version> {
return sb.toString();
}
/**
* Parses the {@literal <alphanumeric identifier>} non-terminal.
*
* <pre>
* {@literal
* <alphanumeric identifier> ::= <non-digit>
* | <non-digit> <identifier characters>
* | <identifier characters> <non-digit>
* | <identifier characters> <non-digit> <identifier characters>
* }
* </pre>
*
* @return a string representing the alphanumeric identifier
*/
private String alphanumericIdentifier() {
StringBuilder sb = new StringBuilder();
sb.append(chars.consume(DIGIT, LETTER, HYPHEN));
@ -224,6 +392,18 @@ class VersionParser implements Parser<Version> {
return sb.toString();
}
/**
* Parses the {@literal <digits>} non-terminal.
*
* <pre>
* {@literal
* <digits> ::= <digit>
* | <digit> <digits>
* }
* </pre>
*
* @return a string representing the digits
*/
private String digits() {
StringBuilder sb = new StringBuilder();
sb.append(chars.consume(DIGIT));
@ -233,6 +413,13 @@ class VersionParser implements Parser<Version> {
return sb.toString();
}
/**
* Chooses the closest character.
*
* @param tryThis the character to try first
* @param orThis the character to fallback to
* @return the closest character
*/
private Char closestEndpoint(Char tryThis, Char orThis) {
if (chars.positiveLookaheadBefore(orThis, tryThis)) {
return tryThis;
@ -240,6 +427,11 @@ class VersionParser implements Parser<Version> {
return orThis;
}
/**
* Checks for leading zeroes in the numeric identifiers.
*
* @throws GrammarException if a numeric identifier has leading zero(es)
*/
private void checkForLeadingZeroes() {
Character la1 = chars.lookahead(1);
Character la2 = chars.lookahead(2);
@ -250,6 +442,12 @@ class VersionParser implements Parser<Version> {
}
}
/**
* Checks for empty identifiers in the pre-release version or build metadata.
*
* @throws GrammarException if the pre-release version or build
* metadata have empty identifier(s)
*/
private void checkForEmptyIdentifier() {
if (DOT.isMatchedBy(chars.lookahead(1))) {
throw new GrammarException("Identifiers MUST NOT be empty");

View File

@ -26,19 +26,42 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the logical "and" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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);

View File

@ -26,17 +26,35 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the comparison "equal" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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.equals(parsedVersion);

View File

@ -26,9 +26,20 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* 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 <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public interface Expression {
/**
* Interprets the expression.
*
* @param version the version to interpret against
* @return the result of the expression interpretation
*/
boolean interpret(Version version);
}

View File

@ -33,28 +33,72 @@ import java.util.Iterator;
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
/**
* A parser for the SemVer Expressions.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public class ExpressionParser implements Parser<Expression> {
/**
* The lexer instance used for tokenization of the input string.
*/
private final Lexer lexer;
/**
* The stream of tokens produced by the lexer.
*/
private Stream<Token> 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<Expression> 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 encounters an unexpected token type
*/
@Override
public Expression parse(String input) {
tokens = lexer.tokenize(input);
return parseSemVerExpression();
}
/**
* Parses the {@literal <semver-expr>} non-terminal.
*
* <pre>
* {@literal
* <semver-expr> ::= "!" "(" <semver-expr> ")"
* | "(" <semver-expr> ")"
* | <semver-expr> <boolean-expr>
* | <expr>
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseSemVerExpression() {
Expression expr;
if (tokens.positiveLookahead(NOT)) {
@ -72,6 +116,19 @@ public class ExpressionParser implements Parser<Expression> {
return parseBooleanExpression(expr);
}
/**
* Parses the {@literal <boolean-expr>} non-terminal.
*
* <pre>
* {@literal
* <boolean-expr> ::= <boolean-op> <semver-expr>
* | <epsilon>
* }
* </pre>
*
* @param expr the left-hand expression of the logical operators
* @return the expression AST
*/
private Expression parseBooleanExpression(Expression expr) {
if (tokens.positiveLookahead(AND)) {
tokens.consume();
@ -83,6 +140,20 @@ public class ExpressionParser implements Parser<Expression> {
return expr;
}
/**
* Parses the {@literal <expr>} non-terminal.
*
* <pre>
* {@literal
* <expr> ::= <comparison-expr>
* | <version-expr>
* | <tilde-expr>
* | <range-expr>
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseExpression() {
if (tokens.positiveLookahead(TILDE)) {
return parseTildeExpression();
@ -94,6 +165,18 @@ public class ExpressionParser implements Parser<Expression> {
return parseComparisonExpression();
}
/**
* Parses the {@literal <comparison-expr>} non-terminal.
*
* <pre>
* {@literal
* <comparison-expr> ::= <comparison-op> <version>
* | <version>
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseComparisonExpression() {
Token token = tokens.lookahead();
Expression expr;
@ -128,6 +211,17 @@ public class ExpressionParser implements Parser<Expression> {
return expr;
}
/**
* Parses the {@literal <tilde-expr>} non-terminal.
*
* <pre>
* {@literal
* <tilde-expr> ::= "~" <version>
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseTildeExpression() {
tokens.consume(TILDE);
int major = intOf(tokens.consume(NUMERIC).lexeme);
@ -150,10 +244,30 @@ public class ExpressionParser implements Parser<Expression> {
);
}
/**
* Determines if the following version terminals are part
* of the {@literal <version-expr>} not-terminal.
*
* @return {@code true} if the following version terminals are
* part of the {@literal <version-expr>} not-terminal or
* {@code false} otherwise
*/
private boolean isVersionExpression() {
return isVersionFollowedBy(STAR);
}
/**
* Parses the {@literal <version-expr>} non-terminal.
*
* <pre>
* {@literal
* <version-expr> ::= <major> "." "*"
* | <major> "." <minor> "." "*"
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseVersionExpression() {
int major = intOf(tokens.consume(NUMERIC).lexeme);
tokens.consume(DOT);
@ -173,10 +287,29 @@ public class ExpressionParser implements Parser<Expression> {
);
}
/**
* Determines if the following version terminals are
* part of the {@literal <range-expr>} not-terminal.
*
* @return {@code true} if the following version terminals are
* part of the {@literal <range-expr>} not-terminal or
* {@code false} otherwise
*/
private boolean isRangeExpression() {
return isVersionFollowedBy(HYPHEN);
}
/**
* Parses the {@literal <range-expr>} non-terminal.
*
* <pre>
* {@literal
* <range-expr> ::= <version> "-" <version>
* }
* </pre>
*
* @return the expression AST
*/
private Expression parseRangeExpression() {
Expression ge = new GreaterOrEqual(parseVersion());
tokens.consume(HYPHEN);
@ -184,6 +317,19 @@ public class ExpressionParser implements Parser<Expression> {
return new And(ge, le);
}
/**
* Parses the {@literal <version>} non-terminal.
*
* <pre>
* {@literal
* <version> ::= <major>
* | <major> "." <minor>
* | <major> "." <minor> "." <patch>
* }
* </pre>
*
* @return the parsed version
*/
private Version parseVersion() {
int major = intOf(tokens.consume(NUMERIC).lexeme);
int minor = 0;
@ -199,6 +345,17 @@ public class ExpressionParser implements Parser<Expression> {
return versionOf(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<Token> type) {
EnumSet<Token.Type> expected = EnumSet.of(NUMERIC, DOT);
Iterator<Token> it = tokens.iterator();
@ -212,10 +369,24 @@ public class ExpressionParser implements Parser<Expression> {
return type.isMatchedBy(lookahead);
}
/**
* Creates a {@code Version} instance for the specified integers.
*
* @param major the major version number
* @param minor the minor version number
* @param patch the patch version number
* @return the version for the specified integers
*/
private Version versionOf(int major, int minor, int patch) {
return Version.forIntegers(major, minor, patch);
}
/**
* Returns a {@code int} representation of the specified string.
*
* @param value the string to convert into an integer
* @return the integer value of the specified string
*/
private int intOf(String value) {
return Integer.parseInt(value);
}

View File

@ -26,17 +26,36 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the comparison "greater than" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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.greaterThan(parsedVersion);

View File

@ -26,17 +26,37 @@ 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 <zafarkhaja@gmail.com>
* @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.greaterThanOrEqualTo(parsedVersion);

View File

@ -26,17 +26,36 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the comparison "less than" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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.lessThan(parsedVersion);

View File

@ -26,17 +26,37 @@ 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 <zafarkhaja@gmail.com>
* @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.lessThanOrEqualTo(parsedVersion);

View File

@ -30,13 +30,21 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A lexer for the SemVer Expressions.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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<Token> {
NUMERIC("0|[1-9][0-9]*"),
@ -57,23 +65,44 @@ class Lexer {
RIGHT_PAREN("\\)"),
WHITESPACE("\\s+"),
EOL("?!") {
/**
* {@inheritDoc}
*/
@Override
public boolean isMatchedBy(Token token) {
return token == null;
}
};
/**
* 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
*/
private 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) {
@ -83,14 +112,30 @@ class Lexer {
}
}
/**
* The type of this token.
*/
final Type type;
/**
* The lexeme of this token.
*/
final String lexeme;
/**
* Constructs a {@code Token} instance with the type and lexeme.
*
* @param type the type of this token
* @param lexeme the lexeme of this token
*/
Token(Type type, String lexeme) {
this.type = type;
this.lexeme = (lexeme == null) ? "" : lexeme;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
if (this == other) {
@ -103,6 +148,9 @@ class Lexer {
return type.equals(token.type) && lexeme.equals(token.lexeme);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = 5;
@ -111,16 +159,31 @@ class Lexer {
return hash;
}
/**
* Returns the string representation of this token.
*
* @return the string representation of this token
*/
@Override
public String toString() {
return type.name() + "(" + lexeme + ")";
}
}
/**
* 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<Token> tokenize(String input) {
List<Token> tokens = new ArrayList<Token>();
while (!input.isEmpty()) {

View File

@ -24,17 +24,34 @@
package com.github.zafarkhaja.semver.expr;
/**
* Thrown during the lexical analysis when
* an illegal character is encountered.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public class LexerException extends RuntimeException {
/**
* 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) {
this.expr = expr;
}
/**
* Returns the string representation of this exception.
*
* @return the string representation of this exception
*/
@Override
public String toString() {
return "Illegal character near '" + expr + "'";

View File

@ -26,17 +26,34 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the logical "negation" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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);

View File

@ -26,17 +26,35 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the comparison "not equal" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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.equals(parsedVersion);

View File

@ -26,19 +26,42 @@ package com.github.zafarkhaja.semver.expr;
import com.github.zafarkhaja.semver.Version;
/**
* Expression for the logical "or" operator.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @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);

View File

@ -28,19 +28,42 @@ import com.github.zafarkhaja.semver.expr.Lexer.*;
import java.util.Arrays;
/**
* Thrown when a token of unexpected types is encountered during the parsing.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
public class UnexpectedTokenException extends ParserException {
/**
* 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 unexpected token and the expected types.
*
* @param element the unexpected token
* @param expected an array of the expected token types
*/
UnexpectedTokenException(Token token, Token.Type... expected) {
unexpected = token;
this.expected = 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() {
String message = "Unexpected token '" + unexpected + "'";

View File

@ -0,0 +1,34 @@
/*
* The MIT License
*
* Copyright 2013 Zafar Khaja <zafarkhaja@gmail.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* 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 <zafarkhaja@gmail.com>
* @since 0.7.0
*/
package com.github.zafarkhaja.semver.expr;

View File

@ -0,0 +1,35 @@
/*
* The MIT License
*
* Copyright 2013 Zafar Khaja <zafarkhaja@gmail.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* 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 <zafarkhaja@gmail.com>
* @since 0.1.0
*/
package com.github.zafarkhaja.semver;

View File

@ -28,23 +28,65 @@ import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A simple stream class used to represent a stream of characters or tokens.
*
* @param <E> the type of elements held in this stream
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @see com.github.zafarkhaja.semver.VersionParser
* @see com.github.zafarkhaja.semver.expr.Lexer
* @see com.github.zafarkhaja.semver.expr.ExpressionParser
* @since 0.7.0
*/
public class Stream<E> implements Iterable<E> {
/**
* The {@code ElementType} interface represents types of the elements
* held by this stream and can be used for stream filtering.
*
* @param <E> type of elements held by this stream
*/
public static interface ElementType<E> {
/**
* 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;
@ -52,6 +94,14 @@ public class Stream<E> implements Iterable<E> {
return elements[offset++];
}
/**
* Consumes the next element in this stream
* only if it is of the expected types.
*
* @param expected the types which are expected
* @return the next element in this stream
* @throws UnexpectedElementTypeException if the next element is of an unexpected type
*/
public E consume(ElementType<E>... expected) {
E lookahead = lookahead(1);
for (ElementType<E> type : expected) {
@ -62,10 +112,23 @@ public class Stream<E> implements Iterable<E> {
throw new UnexpectedElementTypeException(lookahead, expected);
}
/**
* 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) {
@ -74,6 +137,13 @@ public class Stream<E> implements Iterable<E> {
return null;
}
/**
* Checks if the next element in this stream is of the expected types.
*
* @param expected the expected types
* @return {@code true} if the next element is of the expected types
* or {@code false} otherwise
*/
public boolean positiveLookahead(ElementType<E>... expected) {
for (ElementType<E> type : expected) {
if (type.isMatchedBy(lookahead(1))) {
@ -83,6 +153,15 @@ public class Stream<E> implements Iterable<E> {
return false;
}
/**
* Checks if there exists an element in this stream of
* the expected types before the specified type.
*
* @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
*/
public boolean positiveLookaheadBefore(
ElementType<E> before,
ElementType<E>... expected
@ -102,6 +181,15 @@ public class Stream<E> implements Iterable<E> {
return false;
}
/**
* Checks if there is an element in this stream of
* the expected types until the specified position.
*
* @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
*/
public boolean positiveLookaheadUntil(
int until,
ElementType<E>... expected
@ -116,17 +204,28 @@ public class Stream<E> implements Iterable<E> {
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<E> iterator() {
return new Iterator<E>() {
private int index = offset;
/**
* {@inheritDoc}
*/
@Override
public boolean hasNext() {
return index < elements.length;
}
/**
* {@inheritDoc}
*/
@Override
public E next() {
if (index >= elements.length) {
@ -135,6 +234,9 @@ public class Stream<E> implements Iterable<E> {
return elements[index++];
}
/**
* {@inheritDoc}
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
@ -142,6 +244,14 @@ public class Stream<E> implements Iterable<E> {
};
}
/**
* 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);
}

View File

@ -27,19 +27,43 @@ 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 <zafarkhaja@gmail.com>
* @see Stream#consume(Stream.ElementType...)
* @since 0.7.0
*/
public class UnexpectedElementTypeException extends RuntimeException {
/**
* The unexpected element in the stream.
*/
private final Object unexpected;
/**
* The array of the expected element types.
*/
private final ElementType<?>[] expected;
/**
* Constructs a {@code UnexpectedElementTypeException} instance
* with the unexpected element and the expected types.
*
* @param element the unexpected element in the stream
* @param expected an array of the expected element types
*/
UnexpectedElementTypeException(Object element, ElementType<?>... expected) {
unexpected = element;
this.expected = 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() {
String message = "Unexpected element '" + unexpected + "'";

View File

@ -0,0 +1,31 @@
/*
* The MIT License
*
* Copyright 2013 Zafar Khaja <zafarkhaja@gmail.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* This package provides some useful utility classes.
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
* @since 0.7.0
*/
package com.github.zafarkhaja.semver.util;