/** * 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.hadoop.util; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.hadoop.classification.InterfaceAudience; import com.google.common.collect.ComparisonChain; @InterfaceAudience.Private public abstract class VersionUtil { private static final Pattern COMPONENT_GROUPS = Pattern.compile("(\\d+)|(\\D+)"); /** * Suffix added by maven for nightly builds and other snapshot releases. * These releases are considered to precede the non-SNAPSHOT version * with the same version number. */ private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT"; /** * This function splits the two versions on "." and performs a * naturally-ordered comparison of the resulting components. For example, the * version string "0.3" is considered to precede "0.20", despite the fact that * lexical comparison would consider "0.20" to precede "0.3". This method of * comparison is similar to the method used by package versioning systems like * deb and RPM. * * Version components are compared numerically whenever possible, however a * version component can contain non-numeric characters. When a non-numeric * group of characters is found in a version component, this group is compared * with the similarly-indexed group in the other version component. If the * other group is numeric, then the numeric group is considered to precede the * non-numeric group. If both groups are non-numeric, then a lexical * comparison is performed. * * If two versions have a different number of components, then only the lower * number of components are compared. If those components are identical * between the two versions, then the version with fewer components is * considered to precede the version with more components. * * In addition to the above rules, there is one special case: maven SNAPSHOT * releases are considered to precede a non-SNAPSHOT release with an * otherwise identical version number. For example, 2.0-SNAPSHOT precedes * 2.0. * * This function returns a negative integer if version1 precedes version2, a * positive integer if version2 precedes version1, and 0 if and only if the * two versions' components are identical in value and cardinality. * * @param version1 * the first version to compare * @param version2 * the second version to compare * @return a negative integer if version1 precedes version2, a positive * integer if version2 precedes version1, and 0 if and only if the two * versions are equal. */ public static int compareVersions(String version1, String version2) { boolean isSnapshot1 = version1.endsWith(SNAPSHOT_SUFFIX); boolean isSnapshot2 = version2.endsWith(SNAPSHOT_SUFFIX); version1 = stripSnapshotSuffix(version1); version2 = stripSnapshotSuffix(version2); String[] version1Parts = version1.split("\\."); String[] version2Parts = version2.split("\\."); for (int i = 0; i < version1Parts.length && i < version2Parts.length; i++) { String component1 = version1Parts[i]; String component2 = version2Parts[i]; if (!component1.equals(component2)) { Matcher matcher1 = COMPONENT_GROUPS.matcher(component1); Matcher matcher2 = COMPONENT_GROUPS.matcher(component2); while (matcher1.find() && matcher2.find()) { String group1 = matcher1.group(); String group2 = matcher2.group(); if (!group1.equals(group2)) { if (isNumeric(group1) && isNumeric(group2)) { return Integer.parseInt(group1) - Integer.parseInt(group2); } else if (!isNumeric(group1) && !isNumeric(group2)) { return group1.compareTo(group2); } else { return isNumeric(group1) ? -1 : 1; } } } return component1.length() - component2.length(); } } return ComparisonChain.start() .compare(version1Parts.length, version2Parts.length) .compare(isSnapshot2, isSnapshot1) .result(); } private static String stripSnapshotSuffix(String version) { if (version.endsWith(SNAPSHOT_SUFFIX)) { return version.substring(0, version.length() - SNAPSHOT_SUFFIX.length()); } else { return version; } } private static boolean isNumeric(String s) { try { Integer.parseInt(s); return true; } catch (NumberFormatException nfe) { return false; } } }