/* * Copyright © 2015 Cask Data, Inc. * * Licensed 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 co.cask.cdap.api.artifact; import co.cask.cdap.api.annotation.Beta; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Represents version of an artifact. It will try to parse the version string of format * * <pre> * {@code * parse version string in format of [major].[minor].[fix](-|.)[suffix] * } * </pre> */ @Beta public final class ArtifactVersion implements Comparable<ArtifactVersion> { private static final String VERSION_REGEX = "(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:[.\\-](.*))?$"; private static final Pattern PATTERN = Pattern.compile(VERSION_REGEX); private static final Pattern SUFFIX_PATTERN = Pattern.compile("\\-" + VERSION_REGEX); private final String version; private final Integer major; private final Integer minor; private final Integer fix; private final String suffix; /** * Constructs an instance by parsing the given string. If the string does not match the version pattern, * {@link #getVersion()} will return null. * * @param str the version string. The whole string needs to match with the version pattern supported by this class. */ public ArtifactVersion(String str) { this(str, false); } /** * Constructs an instance by parsing the given string. If the string does not match the version pattern, * {@link #getVersion()} will return null. * * @param str the version string. * @param matchSuffix if {@code true}, try to match the version pattern by the suffix of the string. Otherwise match * the whole string. */ public ArtifactVersion(String str, boolean matchSuffix) { String tmpVersion = null; Integer major = null; Integer minor = null; Integer fix = null; String suffix = null; if (str != null) { Matcher matcher = matchSuffix ? SUFFIX_PATTERN.matcher(str) : PATTERN.matcher(str); boolean matches = matchSuffix ? (matcher.find()) : matcher.matches(); if (matches) { tmpVersion = matchSuffix ? matcher.group(0).substring(1) : matcher.group(0); major = valueOf(matcher.group(1)); minor = valueOf(matcher.group(2)); fix = valueOf(matcher.group(3)); suffix = matcher.group(4); } } this.version = tmpVersion; this.major = major; this.minor = minor; this.fix = fix; this.suffix = suffix; } @Nullable public String getVersion() { return version; } @Nullable public Integer getMajor() { return major; } @Nullable public Integer getMinor() { return minor; } @Nullable public Integer getFix() { return fix; } @Nullable public String getSuffix() { return suffix; } public boolean isSnapshot() { return suffix != null && !suffix.isEmpty() && suffix.toLowerCase().startsWith("snapshot"); } @Override public int compareTo(ArtifactVersion other) { int cmp = compare(major, other.major); if (cmp != 0) { return cmp; } cmp = compare(minor, other.minor); if (cmp != 0) { return cmp; } cmp = compare(fix, other.fix); if (cmp != 0) { return cmp; } // All numerical part of the version are the same, compare the suffix. // A special case is no suffix is "greater" than with suffix. This is usually true (e.g. release > snapshot) if (suffix == null) { return other.suffix == null ? 0 : 1; } return other.suffix == null ? -1 : suffix.compareTo(other.suffix); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ArtifactVersion that = (ArtifactVersion) o; return !(version != null ? !version.equals(that.version) : that.version != null); } @Override public int hashCode() { return version != null ? version.hashCode() : 0; } @Override public String toString() { return version; } /** * Compares two {@link Comparable}s that can be null. Returns -1, 0, 1 if first is smaller, equal, larger than second, * based on comparison defined by the {@link Comparable}. * The {@code null} value is smaller than any non-null value and only equals to {@code null}. */ private <T extends Comparable<T>> int compare(@Nullable T first, @Nullable T second) { if (first == null && second == null) { return 0; } if (first == null) { return -1; } if (second == null) { return 1; } return first.compareTo(second); } /** * Parses the given string as integer. If failed, returns {@code null}. */ @Nullable private Integer valueOf(@Nullable String str) { try { if (str == null) { return null; } return Integer.valueOf(str); } catch (NumberFormatException e) { return null; } } }