/* * Copyright (C) 2013 Google 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 interactivespaces.resource; import interactivespaces.SimpleInteractiveSpacesException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A version for a resource. * * <p> * Qualifiers are ignored for comparison reasons. * * @author Keith M. Hughes */ public class Version implements Comparable<Version> { /** * The separator for the version components. */ public static final char VERSION_SECTION_SEPARATOR = '.'; /** * The regular expression for recognizing a version qualifier. */ public static final String QUALIFIER_REGEX = "[a-zA-Z0-9_\\-]+"; /** * Pattern for the version. */ public static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(\\." + QUALIFIER_REGEX + ")?$"); /** * The regex group that gives the major portion of the version. */ public static final int REGEX_GROUP_NUMBER_MAJOR = 1; /** * The regex group that gives the minor portion of the version. */ public static final int REGEX_GROUP_NUMBER_MINOR = 2; /** * The regex group that gives the micro portion of the version. */ public static final int REGEX_GROUP_NUMBER_MICRO = 3; /** * The regex group that gives the qualifier portion of the version. */ public static final int REGEX_GROUP_NUMBER_QUALIFIER = 4; /** * Pattern for just the qualifier. */ public static final Pattern QUALIFIER_PATTERN = Pattern.compile("^" + QUALIFIER_REGEX + "$"); /** * Description of a legal version. */ public static final String VERSION_FORMAT_DESCRIPTION = "A version must be of the form major.minor.micro\n" + "where each section is a series of digits.\n" + "It can also be of the form major.minor.micro.qualifier where qualifier starts with\n" + "a letter or digit, and is followed by letters, digits, -,\n" + "or underscores."; /** * Is the candidate have legal syntax for a version? * * @param candidate * the candidate version * * @return {@code true} if legal syntax */ public static boolean isLegalSyntax(String candidate) { return Version.VERSION_PATTERN.matcher(candidate).matches(); } /** * Parse a version string. * * @param version * the version as a string, can be {@code null} * * @return the version represented by the string, or {@code null} if version was {@code null} * * @throws SimpleInteractiveSpacesException * improperly formatted version */ public static Version parseVersionIncludeNull(String version) throws SimpleInteractiveSpacesException { return version != null ? parseVersion(version) : null; } /** * Parse a version string. * * @param version * the version as a string * * @return the version represented by the string * * @throws SimpleInteractiveSpacesException * improperly formatted version or a {@code null} string */ public static Version parseVersion(String version) throws SimpleInteractiveSpacesException { if (version != null) { version = version.trim(); Matcher matcher = VERSION_PATTERN.matcher(version); if (matcher.matches()) { String major = matcher.group(REGEX_GROUP_NUMBER_MAJOR); String minor = matcher.group(REGEX_GROUP_NUMBER_MINOR); String micro = matcher.group(REGEX_GROUP_NUMBER_MICRO); String qualifier = matcher.group(REGEX_GROUP_NUMBER_QUALIFIER); if (qualifier != null) { qualifier = qualifier.substring(1); } return new Version(Integer.parseInt(major), (minor != null ? Integer.parseInt(minor.substring(1)) : 0), (micro != null ? Integer.parseInt(micro.substring(1)) : 0), qualifier); } else { throw new SimpleInteractiveSpacesException(String.format("Illegal version %s", version)); } } else { throw new SimpleInteractiveSpacesException("Illegal version null"); } } /** * The major version. */ private final int major; /** * The minor version. */ private final int minor; /** * The micro version. */ private final int micro; /** * Any qualifier. */ private final String qualifier; /** * Construct a version. * * @param major * major version * @param minor * minor version * @param micro * micro version * @param qualifier * qualifier, can be {@code null} */ public Version(int major, int minor, int micro, String qualifier) { if (qualifier == null) { qualifier = ""; } this.major = major; this.minor = minor; this.micro = micro; this.qualifier = qualifier; validate(); } /** * Construct a version with a null qualifier. * * @param major * major version * @param minor * minor version * @param micro * micro version */ public Version(int major, int minor, int micro) { this(major, minor, micro, null); } /** * Get the major number. * * @return the major number */ public int getMajor() { return major; } /** * Get the minor number. * * @return the minor number */ public int getMinor() { return minor; } /** * Get the micro number. * * @return the micro number */ public int getMicro() { return micro; } /** * Get the qualifier. * * @return the qualifier */ public String getQualifier() { return qualifier; } /** * Is the current version strictly less than the other version? * * @param other * the other version * * @return {@code true} if less than */ public boolean lessThan(Version other) { return compareTo(other) < 0; } /** * Is the current version less than or equal the other version? * * @param other * the other version * * @return {@code true} if less than or equal */ public boolean lessThanOrEqual(Version other) { return compareTo(other) <= 0; } /** * Is the current version strictly greater than the other version? * * @param other * the other version * * @return {@code true} if strictly greater than */ public boolean greaterThan(Version other) { return compareTo(other) > 0; } /** * Is the current version greater than or equal the other version? * * @param other * the other version * * @return {@code true} if greater than or equal */ public boolean greaterThanOrEqual(Version other) { return compareTo(other) >= 0; } /** * Get a new version with an incremented minor. The minor and micro are then {@code 0}. The qualifier is left alone. * * @return a newly constructed version */ public Version incrementMajor() { return new Version(major + 1, 0, 0, qualifier); } /** * Get a new version with an incremented minor. The micro is then {@code 0}. The qualifier is left alone. * * @return a newly constructed version */ public Version incrementMinor() { return new Version(major, minor + 1, 0, qualifier); } /** * Get a new version with an incremented micro. The qualifier is left alone. * * @return a newly constructed version */ public Version incrementMicro() { return new Version(major, minor, micro + 1, qualifier); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + micro; result = prime * result + major; result = prime * result + minor; result = prime * result + qualifier.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Version other = (Version) obj; return (major == other.major) && (minor == other.minor) && (micro == other.micro) && qualifier.equals(other.qualifier); } @Override public int compareTo(Version o) { if (o == this) { return 0; } int diff = major - o.major; if (diff == 0) { diff = minor - o.minor; if (diff == 0) { diff = micro - o.micro; if (diff == 0) { diff = qualifier.compareTo(o.qualifier); } } } return diff; } @Override public String toString() { StringBuilder builder = new StringBuilder().append(major).append(VERSION_SECTION_SEPARATOR).append(minor) .append(VERSION_SECTION_SEPARATOR).append(micro); if (!qualifier.isEmpty()) { builder.append(VERSION_SECTION_SEPARATOR).append(qualifier); } return builder.toString(); } /** * Validate the version components. */ private void validate() { if (major < 0) { throw new SimpleInteractiveSpacesException(String.format("Major version number cannot be negative: %d", major)); } if (minor < 0) { throw new SimpleInteractiveSpacesException(String.format("Minor version number cannot be negative: %d", minor)); } if (micro < 0) { throw new SimpleInteractiveSpacesException(String.format("Micro version number cannot be negative: %d", micro)); } if (!qualifier.isEmpty() && !Version.QUALIFIER_PATTERN.matcher(qualifier).matches()) { throw new SimpleInteractiveSpacesException(String.format( "Version qualifiers must use only characters a-z, A-Z, 0-9, _ and - cannot be negative: %s", qualifier)); } } }