/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.cassandra.utils;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Objects;
import org.apache.commons.lang3.StringUtils;

Implements versioning used in Cassandra and CQL.

Note: The following code uses a slight variation from the semver document (http://semver.org).

/** * Implements versioning used in Cassandra and CQL. * <p> * Note: The following code uses a slight variation from the semver document (http://semver.org). * </p> */
public class CassandraVersion implements Comparable<CassandraVersion> {
note: 3rd group matches to words but only allows number and checked after regexp test. this is because 3rd and the last can be identical.
/** * note: 3rd group matches to words but only allows number and checked after regexp test. * this is because 3rd and the last can be identical. **/
private static final String VERSION_REGEXP = "(\\d+)\\.(\\d+)(?:\\.(\\w+))?(\\-[.\\w]+)?([.+][.\\w]+)?"; private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\w+"); private static final Pattern pattern = Pattern.compile(VERSION_REGEXP); private static final Pattern SNAPSHOT = Pattern.compile("-SNAPSHOT"); public final int major; public final int minor; public final int patch; private final String[] preRelease; private final String[] build;
Parse a version from a string.
Params:
  • version – the string to parse
Throws:
/** * Parse a version from a string. * * @param version the string to parse * @throws IllegalArgumentException if the provided string does not * represent a version */
public CassandraVersion(String version) { String stripped = SNAPSHOT.matcher(version).replaceFirst(""); Matcher matcher = pattern.matcher(stripped); if (!matcher.matches()) throw new IllegalArgumentException("Invalid version value: " + version); try { this.major = Integer.parseInt(matcher.group(1)); this.minor = Integer.parseInt(matcher.group(2)); this.patch = matcher.group(3) != null ? Integer.parseInt(matcher.group(3)) : 0; String pr = matcher.group(4); String bld = matcher.group(5); this.preRelease = pr == null || pr.isEmpty() ? null : parseIdentifiers(stripped, pr); this.build = bld == null || bld.isEmpty() ? null : parseIdentifiers(stripped, bld); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid version value: " + version, e); } } private static String[] parseIdentifiers(String version, String str) { // Drop initial - or + str = str.substring(1); String[] parts = StringUtils.split(str, '.'); for (String part : parts) { if (!PATTERN_WHITESPACE.matcher(part).matches()) throw new IllegalArgumentException("Invalid version value: " + version); } return parts; } public int compareTo(CassandraVersion other) { if (major < other.major) return -1; if (major > other.major) return 1; if (minor < other.minor) return -1; if (minor > other.minor) return 1; if (patch < other.patch) return -1; if (patch > other.patch) return 1; int c = compareIdentifiers(preRelease, other.preRelease, 1); if (c != 0) return c; return compareIdentifiers(build, other.build, -1); } public boolean is30() { return major == 3 && minor == 0; }
Returns a version that is backward compatible with this version amongst a list of provided version, or null if none can be found.

For instance: "2.0.0".findSupportingVersion("2.0.0", "3.0.0") == "2.0.0" "2.0.0".findSupportingVersion("2.1.3", "3.0.0") == "2.1.3" "2.0.0".findSupportingVersion("3.0.0") == null "2.0.3".findSupportingVersion("2.0.0") == "2.0.0" "2.1.0".findSupportingVersion("2.0.0") == null

/** * Returns a version that is backward compatible with this version amongst a list * of provided version, or null if none can be found. * <p> * For instance: * "2.0.0".findSupportingVersion("2.0.0", "3.0.0") == "2.0.0" * "2.0.0".findSupportingVersion("2.1.3", "3.0.0") == "2.1.3" * "2.0.0".findSupportingVersion("3.0.0") == null * "2.0.3".findSupportingVersion("2.0.0") == "2.0.0" * "2.1.0".findSupportingVersion("2.0.0") == null * </p> */
public CassandraVersion findSupportingVersion(CassandraVersion... versions) { for (CassandraVersion version : versions) { if (isSupportedBy(version)) return version; } return null; } public boolean isSupportedBy(CassandraVersion version) { return version != null && major == version.major && this.compareTo(version) <= 0; } private static int compareIdentifiers(String[] ids1, String[] ids2, int defaultPred) { if (ids1 == null) return ids2 == null ? 0 : defaultPred; else if (ids2 == null) return -defaultPred; int min = Math.min(ids1.length, ids2.length); for (int i = 0; i < min; i++) { Integer i1 = tryParseInt(ids1[i]); Integer i2 = tryParseInt(ids2[i]); if (i1 != null) { // integer have precedence if (i2 == null || i1 < i2) return -1; else if (i1 > i2) return 1; } else { // integer have precedence if (i2 != null) return 1; int c = ids1[i].compareTo(ids2[i]); if (c != 0) return c; } } if (ids1.length < ids2.length) return -1; if (ids1.length > ids2.length) return 1; return 0; } private static Integer tryParseInt(String str) { try { return Integer.valueOf(str); } catch (NumberFormatException e) { return null; } } @Override public boolean equals(Object o) { if (!(o instanceof CassandraVersion)) return false; CassandraVersion that = (CassandraVersion) o; return major == that.major && minor == that.minor && patch == that.patch && Arrays.equals(preRelease, that.preRelease) && Arrays.equals(build, that.build); } @Override public int hashCode() { return Objects.hashCode(major, minor, patch, preRelease, build); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(major).append('.').append(minor).append('.').append(patch); if (preRelease != null) sb.append('-').append(StringUtils.join(preRelease, ".")); if (build != null) sb.append('+').append(StringUtils.join(build, ".")); return sb.toString(); } }