All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.zafarkhaja.semver.VersionParser Maven / Gradle / Ivy

Go to download

Tool that generates code for RPC between the Kurento Media Server and remote libraries.

There is a newer version: 7.1.0
Show newest version
/*
 * 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