/* * Copyright (c) 2012-2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.coordinator.client.model; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.exceptions.CoordinatorException; import com.emc.storageos.coordinator.exceptions.InvalidSoftwareVersionException; public class SoftwareVersion implements Comparable<SoftwareVersion> { private static final Logger log = LoggerFactory.getLogger(SoftwareVersion.class); private static final String SOFTWARE_VERSION_PREFIX = ProductName.getName() + "-"; private static final String SOFTWARE_VERSION_RELEASE_WILDCARD = "*"; private static final String SOFTWARE_VERSION_PATTERN = "(" + SOFTWARE_VERSION_PREFIX + "|)(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\w+|\\" + SOFTWARE_VERSION_RELEASE_WILDCARD + ")"; private static final String SOFTWARE_RELEASE_INT_PATTERN = "\\d+"; private static final Pattern intPattern = Pattern.compile(SOFTWARE_RELEASE_INT_PATTERN); private final String prefix; private final String release; private final int[] versionTuple; public SoftwareVersion(String versionStr) throws InvalidSoftwareVersionException { try { Matcher m = Pattern.compile(SOFTWARE_VERSION_PATTERN).matcher(versionStr); m.find(); prefix = (m.group(1).length() > 0) ? m.group(1) : SOFTWARE_VERSION_PREFIX; versionTuple = new int[m.groupCount() - 2]; for (int i = 0; i < versionTuple.length; i++) { versionTuple[i] = Integer.valueOf(m.group(i + 2)); } release = m.group(m.groupCount()); // Sanity check: the input must match the toString() output String stringRepresent = this.toString(); if (!stringRepresent.equals(versionStr) && !stringRepresent.equals(SOFTWARE_VERSION_PREFIX + versionStr)) { throw new Exception(); } } catch (Exception e) { throw CoordinatorException.fatals.invalidSoftwareVersion("Invalid software version: " + versionStr); } } public static SoftwareVersion toSoftwareVersion(final String versionStr) { try { return new SoftwareVersion(versionStr); } catch (InvalidSoftwareVersionException e) { log.error("toSoftwareVersion(): {}: Skipping.", e); return null; } } public static List<SoftwareVersion> toSoftwareVersionList(final String versionsStr) { if (versionsStr != null) { List<SoftwareVersion> versions = new ArrayList<SoftwareVersion>(); for (String v : versionsStr.split(",")) { try { versions.add(new SoftwareVersion(v)); } catch (InvalidSoftwareVersionException e) { log.error("stringToList(): {}: Skipping.", e); } } if (!versions.isEmpty()) { return versions; } } return null; } public static String listToString(final List<SoftwareVersion> versions) { if (versions != null && !versions.isEmpty()) { StringBuilder s = new StringBuilder(); for (int i = 0; i < versions.size(); i++) { if (i > 0) { s.append(","); } s.append(versions.get(i)); } return s.toString(); } return null; } public String toString() { StringBuilder str = new StringBuilder(); str.append(prefix); int[] tuple = versionTuple; for (int idx = 0; idx < tuple.length; idx++) { str.append(String.valueOf(tuple[idx])); str.append("."); } str.append(release); return str.toString(); } @Override public int hashCode() { return release.hashCode(); } @Override public boolean equals(Object obj) { if (obj != null) { SoftwareVersion version = (SoftwareVersion) obj; if (this.compareTo(version) == 0 && this.release.equals(version.release)) { return true; } } return false; } @Override public int compareTo(SoftwareVersion version) { int[] thisTuple = versionTuple; int[] targetTuple = version.versionTuple; for (int idx = 0; idx < thisTuple.length; idx++) { if (thisTuple[idx] < targetTuple[idx]) { return -1; } else if (thisTuple[idx] > targetTuple[idx]) { return 1; } } // At this point, the version number sequence is compared to be equal. // So compare the release number // If both integer, compare as integer. // if one is integer, integer is greater than string. // otherwise, compare as strings. if (isValidInteger(this.release) && isValidInteger(version.release)) { return Integer.valueOf(this.release).compareTo(Integer.valueOf(version.release)); } if (isValidInteger(this.release)) { return 1; } if (isValidInteger(version.release)) { return -1; } return this.release.compareTo(version.release); } /** * Check if two versions are equal. If there is a wild card in the release then the two versions are equal if the * major/minor number matches. For example 1.0.0.8.* is equal to 1.0.0.8.1 and 1.0.0.8.50 * * @param v version to compare to this version * @return true if the versions are equal accepting wild card releases */ public boolean weakEquals(SoftwareVersion v) { log.info("Version detail info: version release {}, version prefix {}", v.release, v.prefix); if (v.release.equals(SOFTWARE_VERSION_RELEASE_WILDCARD) || this.release.equals(SOFTWARE_VERSION_RELEASE_WILDCARD)) { return this.prefix.equals(v.prefix) && Arrays.equals(versionTuple, v.versionTuple); } return this.equals(v); } /** * Check if it's possible to switch from current version to the target version. The method will check the Version metadata of * the target version image file, if the current version is in the upgradeFromVersionsList or downgradeFromVersionlist, it will return * true. * If the current version is naturally upgradeable to the target version, it returns true as well. * * @param to * @return * @throws IOException */ public boolean isSwitchableTo(SoftwareVersion to) throws IOException { // Must be different from the current version. if (equals(to)) { return false; } if (isNaturallySwitchableTo(to)) { return true; } try { if (compareTo(to) < 0) { SoftwareVersionMetadata versionMetadata = SoftwareVersionMetadata.getInstance(to); for (SoftwareVersion v : versionMetadata.upgradeFromVersionsList) { if (this.weakEquals(v)) { return true; } } } else { SoftwareVersionMetadata versionMetadata = SoftwareVersionMetadata.getInstance(this); for (SoftwareVersion v : versionMetadata.downgradeToVersionsList) { if (v.weakEquals(to)) { return true; } } } return false; } catch (FileNotFoundException e) { log.info("Version " + to + " not found on the system. Switching to this version is not possible."); return false; } } /** * Check if it's possible to upgrade from current version to the target version naturally. * * @param to * @return * @throws IOException */ public boolean isNaturallySwitchableTo(SoftwareVersion to) { // Must be different from the current version. if (equals(to)) { return false; } // All major numbers are the same. int minorIdx = versionTuple.length - 1; for (int idx = 0; idx < minorIdx; idx++) { if (to.versionTuple[idx] != versionTuple[idx]) { return false; } } return true; } /** * Validates whether the version is in valid format. * * @param version * - Input version string * @return true - if input version string is valid false - otherwise */ public static boolean isValid(String version) { try { // This is used to suppress violation of unused instance // Actually, we just detect whether it would throw Exception when instantiating // Exception would be thrown if version is not valid, then return false new SoftwareVersion(version); // NOSONAR("squid:S1848") return true; } catch (InvalidSoftwareVersionException e) { return false; } } /** * Validate whether a string is parseable to integer * * @param str string to match * @return true if it is an integer string; false otherwise. */ private static boolean isValidInteger(String str) { return intPattern.matcher(str).matches(); } }