com.github.zafarkhaja.semver.VersionParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kurento-module-creator Show documentation
Show all versions of kurento-module-creator Show documentation
Tool that generates code for RPC between the Kurento Media Server
and remote libraries.
/*
* The MIT License
*
* Copyright 2012-2014 Zafar Khaja ([email protected]).
*
* 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.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import com.github.zafarkhaja.semver.util.Stream;
import com.github.zafarkhaja.semver.util.UnexpectedElementException;
/**
* A parser for the SemVer Version.
*
* @author Zafar Khaja ([email protected])
* @since 0.7.0
*/
class VersionParser implements Parser {
/**
* Valid character types.
*/
static 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 == '+';
}
},
EOL {
/**
* {@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;
}
}
/**
* 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) {
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) {
VersionParser parser = new VersionParser(version);
return parser.parseValidSemVer();
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
* | "-"
* | "+"
* | "-" "+"
* }
*
*
* @return a valid version object
*/
private Version parseValidSemVer() {
NormalVersion normal = parseVersionCore();
MetadataVersion preRelease = MetadataVersion.NULL;
MetadataVersion build = MetadataVersion.NULL;
Character next = consumeNextCharacter(CharType.HYPHEN, CharType.PLUS, CharType.EOL);
if (CharType.HYPHEN.isMatchedBy(next)) {
preRelease = parsePreRelease();
next = consumeNextCharacter(CharType.PLUS, CharType.EOL);
if (CharType.PLUS.isMatchedBy(next)) {
build = parseBuild();
}
} else if (CharType.PLUS.isMatchedBy(next)) {
build = parseBuild();
}
consumeNextCharacter(CharType.EOL);
return new Version(normal, preRelease, build);
}
/**
* Parses the version core.
*
* @param versionCore
* the version core string to parse
* @return a valid normal 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 NormalVersion parseVersionCore(String versionCore) {
VersionParser parser = new VersionParser(versionCore);
return parser.parseVersionCore();
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::= "." "."
* }
*
*
* @return a valid normal version object
*/
private NormalVersion parseVersionCore() {
int major = Integer.parseInt(numericIdentifier());
consumeNextCharacter(CharType.DOT);
int minor = Integer.parseInt(numericIdentifier());
consumeNextCharacter(CharType.DOT);
int patch = Integer.parseInt(numericIdentifier());
return new NormalVersion(major, minor, patch);
}
/**
* Parses the pre-release version.
*
* @param preRelease
* the pre-release version string to parse
* @return a valid pre-release 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 MetadataVersion parsePreRelease(String preRelease) {
VersionParser parser = new VersionParser(preRelease);
return parser.parsePreRelease();
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
*
* ::=
* | "."
* }
*
*
* @return a valid pre-release version object
*/
private MetadataVersion parsePreRelease() {
ensureValidLookahead(CharType.DIGIT, CharType.LETTER, CharType.HYPHEN);
List idents = new ArrayList();
do {
idents.add(preReleaseIdentifier());
if (chars.positiveLookahead(CharType.DOT)) {
consumeNextCharacter(CharType.DOT);
continue;
}
break;
} while (true);
return new MetadataVersion(idents.toArray(new String[idents.size()]));
}
/**
* Parses the build metadata.
*
* @param build
* the build metadata string to parse
* @return a valid build metadata 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 MetadataVersion parseBuild(String build) {
VersionParser parser = new VersionParser(build);
return parser.parseBuild();
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
*
* ::=
* | "."
* }
*
*
* @return a valid build metadata object
*/
private MetadataVersion parseBuild() {
ensureValidLookahead(CharType.DIGIT, CharType.LETTER, CharType.HYPHEN);
List idents = new ArrayList();
do {
idents.add(buildIdentifier());
if (chars.positiveLookahead(CharType.DOT)) {
consumeNextCharacter(CharType.DOT);
continue;
}
break;
} while (true);
return new MetadataVersion(idents.toArray(new String[idents.size()]));
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
* |
* }
*
*
* @return a single pre-release identifier
*/
private String preReleaseIdentifier() {
checkForEmptyIdentifier();
CharType boundary = nearestCharType(CharType.DOT, CharType.PLUS, CharType.EOL);
if (chars.positiveLookaheadBefore(boundary, CharType.LETTER, CharType.HYPHEN)) {
return alphanumericIdentifier();
} else {
return numericIdentifier();
}
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
* |
* }
*
*
* @return a single build identifier
*/
private String buildIdentifier() {
checkForEmptyIdentifier();
CharType boundary = nearestCharType(CharType.DOT, CharType.EOL);
if (chars.positiveLookaheadBefore(boundary, CharType.LETTER, CharType.HYPHEN)) {
return alphanumericIdentifier();
} else {
return digits();
}
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::= "0"
* |
* |
* }
*
*
* @return a string representing the numeric identifier
*/
private String numericIdentifier() {
checkForLeadingZeroes();
return digits();
}
/**
* Parses the {@literal } non-terminal.
*
*
* {@literal
* ::=
* |
* |
* |
* }
*
*
* @return a string representing the alphanumeric identifier
*/
private String alphanumericIdentifier() {
StringBuilder sb = new StringBuilder();
do {
sb.append(consumeNextCharacter(CharType.DIGIT, CharType.LETTER, CharType.HYPHEN));
} while (chars.positiveLookahead(CharType.DIGIT, CharType.LETTER, CharType.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(CharType.DIGIT));
} while (chars.positiveLookahead(CharType.DIGIT));
return sb.toString();
}
/**
* Finds the nearest character type.
*
* @param types
* the character types to choose from
* @return the nearest character type or {@code EOL}
*/
private CharType nearestCharType(CharType... types) {
for (Character chr : chars) {
for (CharType type : types) {
if (type.isMatchedBy(chr)) {
return type;
}
}
}
return CharType.EOL;
}
/**
* 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' && CharType.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 (CharType.DOT.isMatchedBy(la) || CharType.PLUS.isMatchedBy(la)
|| CharType.EOL.isMatchedBy(la)) {
throw new ParseException("Identifiers MUST NOT be empty", new UnexpectedCharacterException(la,
chars.currentOffset(), CharType.DIGIT, CharType.LETTER, CharType.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);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy