blob: 649c065d043c0d7a3a1b437663c0a7a14e4e052f [file] [log] [blame]
/*
* 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.kafka.message;
import java.util.Objects;
/**
* A version range.
*
* A range consists of two 16-bit numbers: the lowest version which is accepted, and the highest.
* Ranges are inclusive, meaning that both the lowest and the highest version are valid versions.
* The only exception to this is the NONE range, which contains no versions at all.
*
* Version ranges can be represented as strings.
*
* A single supported version V is represented as "V".
* A bounded range from A to B is represented as "A-B".
* All versions greater than A is represented as "A+".
* The NONE range is represented as an the string "none".
*/
public final class Versions {
private final short lowest;
private final short highest;
public static Versions parse(String input, Versions defaultVersions) {
if (input == null) {
return defaultVersions;
}
String trimmedInput = input.trim();
if (trimmedInput.length() == 0) {
return defaultVersions;
}
if (trimmedInput.equals(NONE_STRING)) {
return NONE;
}
if (trimmedInput.endsWith("+")) {
return new Versions(Short.parseShort(
trimmedInput.substring(0, trimmedInput.length() - 1)),
Short.MAX_VALUE);
} else {
int dashIndex = trimmedInput.indexOf("-");
if (dashIndex < 0) {
short version = Short.parseShort(trimmedInput);
return new Versions(version, version);
}
return new Versions(
Short.parseShort(trimmedInput.substring(0, dashIndex)),
Short.parseShort(trimmedInput.substring(dashIndex + 1)));
}
}
public static final Versions ALL = new Versions((short) 0, Short.MAX_VALUE);
public static final Versions NONE = new Versions();
public static final String NONE_STRING = "none";
private Versions() {
this.lowest = 0;
this.highest = -1;
}
public Versions(short lowest, short highest) {
if ((lowest < 0) || (highest < 0)) {
throw new RuntimeException("Invalid version range " +
lowest + " to " + highest);
}
this.lowest = lowest;
this.highest = highest;
}
public short lowest() {
return lowest;
}
public short highest() {
return highest;
}
public boolean empty() {
return lowest > highest;
}
@Override
public String toString() {
if (empty()) {
return NONE_STRING;
} else if (lowest == highest) {
return String.valueOf(lowest);
} else if (highest == Short.MAX_VALUE) {
return String.format("%d+", lowest);
} else {
return String.format("%d-%d", lowest, highest);
}
}
/**
* Return the intersection of two version ranges.
*
* @param other The other version range.
* @return A new version range.
*/
public Versions intersect(Versions other) {
short newLowest = lowest > other.lowest ? lowest : other.lowest;
short newHighest = highest < other.highest ? highest : other.highest;
if (newLowest > newHighest) {
return Versions.NONE;
}
return new Versions(newLowest, newHighest);
}
/**
* Return a new version range that trims some versions from this range, if possible.
* We can't trim any versions if the resulting range would be disjoint.
*
* Some examples:
* 1-4.trim(1-2) = 3-4
* 3+.trim(4+) = 3
* 4+.trim(3+) = none
* 1-5.trim(2-4) = null
*
* @param other The other version range.
* @return A new version range.
*/
public Versions subtract(Versions other) {
if (other.lowest() <= lowest) {
if (other.highest >= highest) {
// Case 1: other is a superset of this. Trim everything.
return Versions.NONE;
} else if (other.highest < lowest) {
// Case 2: other is a disjoint version range that is lower than this. Trim nothing.
return this;
} else {
// Case 3: trim some values from the beginning of this range.
//
// Note: it is safe to assume that other.highest() + 1 will not overflow.
// The reason is because if other.highest() were Short.MAX_VALUE,
// other.highest() < highest could not be true.
return new Versions((short) (other.highest() + 1), highest);
}
} else if (other.highest >= highest) {
int newHighest = other.lowest - 1;
if (newHighest < 0) {
// Case 4: other was NONE. Trim nothing.
return this;
} else if (newHighest < highest) {
// Case 5: trim some values from the end of this range.
return new Versions(lowest, (short) newHighest);
} else {
// Case 6: other is a disjoint range that is higher than this. Trim nothing.
return this;
}
} else {
// Case 7: the difference between this and other would be two ranges, not one.
return null;
}
}
public boolean contains(short version) {
return version >= lowest && version <= highest;
}
public boolean contains(Versions other) {
if (other.empty()) {
return true;
}
return !((lowest > other.lowest) || (highest < other.highest));
}
@Override
public int hashCode() {
return Objects.hash(lowest, highest);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Versions)) {
return false;
}
Versions otherVersions = (Versions) other;
return lowest == otherVersions.lowest &&
highest == otherVersions.highest;
}
}