org.apache.brooklyn.util.text.BrooklynVersionSyntax Maven / Gradle / Ivy
Show all versions of brooklyn-utils-common Show documentation
/*
* 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.brooklyn.util.text;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Preconditions;
/** Utilities for parsing and working with versions following the recommended Brooklyn scheme,
* following major.minor.patch-qualifier
syntax,
* with support for mapping to OSGi.
*
* See tests for examples, {@link VersionComparator} for more notes, and its tests for more examples.
*/
public class BrooklynVersionSyntax {
private static final String SNAPSHOT = "SNAPSHOT";
public static final String USABLE_REGEX = "[^:\\s/\\\\]+";
public static final String DOT = "\\.";
public final static String OSGI_TOKEN_CHARS = "A-Za-z0-9_-";
public final static String OSGI_TOKEN_REGEX = "[" + OSGI_TOKEN_CHARS + "]+";
public final static String NUMBER = "[0-9]+";
public final static String QUALIFIER = OSGI_TOKEN_REGEX;
public final static String VALID_OSGI_VERSION_REGEX =
NUMBER +
"(" + DOT + NUMBER +
"(" + DOT + NUMBER +
"(" + DOT + QUALIFIER +
")?" +
")?" +
")?";
public final static String GOOD_BROOKLYN_VERSION_REGEX =
NUMBER +
"(" + DOT + NUMBER +
"(" + DOT + NUMBER +
"(" + "-" + QUALIFIER +
")?" +
")?" +
")?";
private static boolean isUsable(String candidate) {
return candidate!=null && candidate.matches(USABLE_REGEX);
}
/**
* For versions we currently work with any non-empty string that does not contain a ':' or whitespace.
* However we discourage things that are not OSGi versions; see {@link #isValidOsgiVersion(String)}.
* In some places (eg bundles) the use of OSGi version syntax may be enforced.
*/
public static boolean isUsableVersion(String candidate) {
return isUsable(candidate);
}
/** True if the argument matches the Brooklyn version syntax,
* MAJOR.MINOR.POINT-QUALIFIER
or part thereof (not ending in a period though),
* where the first three are whole numbers and the final piece is any valid OSGi token
* (containing letters, numbers, _ and -; no full stops).
* See also {@link #isValidOsgiVersion(String)} and note this _requires_ a different separator to OSGi.
*/
public static boolean isGoodBrooklynVersion(String candidate) {
return candidate!=null && candidate.matches(GOOD_BROOKLYN_VERSION_REGEX);
}
/** True if the argument matches the OSGi version spec, of the form
* MAJOR.MINOR.POINT.QUALIFIER
or part thereof (not ending in a period though),
* where the first three are whole numbers and the final piece is any valid OSGi token
* (containing letters, numbers, _ and -; no full stops).
* See also {@link #isGoodBrooklynVersion(String)}.
*/
public static boolean isValidOsgiVersion(String candidate) {
return candidate!=null && candidate.matches(VALID_OSGI_VERSION_REGEX);
}
/** Creates a string satisfying {@link #isValidOsgiVersion(String)} based on the input.
* For input satisfying {@link #isGoodBrooklynVersion(String)} the only change will be in the qualifer separator
* (from "-" to ".") and making any "0" minor/patch token explicit (so "1-x" becomes "1.0.0.x"),
* and the change can be reversed using {@link #toGoodBrooklynVersion(String)} (modulo insertion of "0"'s for minor/patch numbers if missing).
* For input satisfying {@link #isValidOsgiVersion(String)}, the only change will be insertions of 0 for minor/patch.
* Precise behaviour for other input is not guaranteed but callers can expect output which resembles the input,
* with any major/minor/patch string at the front preserved and internal contiguous alphanumeric sequences preserved. */
public static String toValidOsgiVersion(String input) {
Preconditions.checkNotNull(input);
return toGoodVersion(input, ".", true);
/* Note Maven has and used: DefaultMaven2OsgiConverter
* from https://github.com/apache/felix/blob/trunk/tools/maven-bundle-plugin/src/main/java/org/apache/maven/shared/osgi/DefaultMaven2OsgiConverter.java
* but it (a) is more complicated, and (b) doesn't aggressively find numbers e.g. "1beta" goes to "0.0.0.1beta" instead of "1.0.0.beta"
*/
}
/**
* See {@link #toValidOsgiVersion(String)}, but takes a version range (in the standard OSGi format).
*/
public static String toValidOsgiVersionRange(String input) {
String beginning = "";
String ending = "";
if (input.startsWith("[") || input.startsWith("(")) {
beginning = input.substring(0, 1);
}
if (input.endsWith("]") || input.endsWith(")")) {
ending = input.substring(input.length() - 1, input.length());
}
String middle = input.substring(beginning.length(), input.length() - ending.length());
String[] middleParts = middle.split(",");
StringBuilder result = new StringBuilder();
result.append(beginning);
for (int i = 0; i < middleParts.length; i++) {
String middlePart = middleParts[i];
if (i != 0) result.append(",");
result.append(toValidOsgiVersion(middlePart.trim()));
}
result.append(ending);
return result.toString();
}
/** Creates a string satisfying {@link #isGoodBrooklynVersion(String)} based on the input.
* For input satisfying {@link #isGoodBrooklynVersion(String)} the input will be returned unchanged.
* For input satisfying {@link #isValidOsgiVersion(String)} the qualifier separator will be changed to "-",
* and {@link #toValidOsgiVersion(String)} can be used to reverse the input (modulo insertion of "0"'s for minor/patch numbers if missing).
* Precise behaviour for other input is not guaranteed but callers can expect output which resembles the input,
* with any major/minor/patch string at the front preserved and internal contiguous alphanumeric sequences preserved. */
public static String toGoodBrooklynVersion(String input) {
return toGoodVersion(input, "-", false);
}
private static String toGoodVersion(String input, String qualifierSeparator, boolean requireMinorAndPatch) {
Preconditions.checkNotNull(input);
final String FUZZY_REGEX =
"(" + NUMBER + "(" + DOT + NUMBER + "(" + DOT + NUMBER + ")?)?)?" +
"(" + ".*)";
Matcher m = Pattern.compile(FUZZY_REGEX).matcher(input);
if (!m.matches()) {
throw new IllegalStateException("fuzzy matcher should match anything: '"+input+"'"); // sanity check - shouldn't happen
}
StringBuilder result = new StringBuilder();
if (Strings.isEmpty(m.group(1))) {
result.append("0.0.0");
} else {
result.append(m.group(1));
if (requireMinorAndPatch) {
if (Strings.isEmpty(m.group(2))) {
result.append(".0");
}
if (Strings.isEmpty(m.group(3))) {
result.append(".0");
}
}
}
String q = m.group(4);
if (Strings.isNonEmpty(q)) {
boolean collapsedUnsupported = false;
boolean starting = true;
result.append(qualifierSeparator);
for (int i=0; i= c) || ('a' <= c && 'z' >= c) || ('0' <= c && '9' >= c);
starting = false;
if (!include) {
if (c=='-' || c=='_' || c=='.') {
// treat these as separator chars, and drop them
if (q.length()==1) {
// unless there are no other chars (e.g. version "1." becomes "1-_"
unsupported = true;
}
} else {
// treat other chars as unsupported
unsupported = true;
}
}
} else {
include = ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || ('0' <= c && '9' >= c) || c=='-' || c=='_';
if (!include) {
// treat as unsupported, unless we've collapsed
if (!collapsedUnsupported) {
unsupported = true;
}
}
}
if (include) {
result.append(c);
collapsedUnsupported = false;
} else if (unsupported) {
// stick a "_" in for unsupported chars
result.append('_');
collapsedUnsupported = true;
}
}
}
return result.toString();
}
/** Returns true if the given strings are equal when mapped according to {@link #toValidOsgiVersion(String)} */
public static boolean equalAsOsgiVersions(String v1, String v2) {
if (v1==null || v2==null) return (v1==null && v2==null);
return toValidOsgiVersion(v1).equals(toValidOsgiVersion(v2));
}
public static boolean isSnapshot(String version) {
if (version==null) return false;
return version.toUpperCase().contains(SNAPSHOT);
}
/**
* Returns the version without "SNAPSHOT" (normally this will return the next expected release version).
* For example, "1.0.0.SNAPSHOT" or "1.0.0-SNAPSHOT" becomes "1.0.0".
*/
public static String stripSnapshot(String input) {
if (input==null) return input;
int stripIndex = input.toUpperCase().indexOf(SNAPSHOT);
if (stripIndex <= 0) return input;
char charBeforeSnapshot = input.charAt(stripIndex - 1);
if (charBeforeSnapshot == '.' || charBeforeSnapshot == '_' || charBeforeSnapshot == '-') {
stripIndex--;
}
return (stripIndex <= 0) ? input : input.substring(0, stripIndex);
}
}