Skip to content

Commit

Permalink
address feedback, add semantic version tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lmolkova committed Aug 17, 2021
1 parent 0fe26da commit dc3c3a4
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 171 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.implementation;

import java.util.Objects;

/**
* Implements lightweight semantic version based on https://semver.org/ for internal use.
*/
public final class SemanticVersion implements Comparable<SemanticVersion> {

/**
* Represents unknown version - either invalid or missing.
*/
public static final String UNKNOWN_VERSION = "unknown";

/**
* Returns implementation version of the package for given class.
*
* @param className - class name to get package version of.
* @return parsed {@link SemanticVersion} or invalid one.
*/
public static SemanticVersion getPackageVersionForClass(String className) {
Objects.requireNonNull(className, "'className' cannot be null.");
try {
return SemanticVersion.getPackageVersion(Class.forName(className));
} catch (ClassNotFoundException e) {
return createInvalid();
}
}

/**
* Parses semver 2.0.0 string.
*
* @param version to parse.
* @return parsed {@link SemanticVersion} or invalid one.
*/
public static SemanticVersion parse(String version) {
Objects.requireNonNull(version, "'version' cannot be null.");
String[] parts = version.split("\\.");
if (parts.length < 3) {
return createInvalid(version);
}

int majorDotIdx = version.indexOf('.');
int minorDotIdx = version.indexOf('.', majorDotIdx + 1);
if (majorDotIdx < 0 || minorDotIdx < 0) {
return createInvalid(version);
}

int patchEndIdx = version.indexOf('-', minorDotIdx + 1);
int extEndIdx = version.indexOf('+', minorDotIdx + 1);

if (patchEndIdx < 0) {
patchEndIdx = version.length();
}

if (extEndIdx < 0) {
extEndIdx = version.length();
}

patchEndIdx = Math.min(patchEndIdx, extEndIdx);


try {
Integer major = Integer.valueOf(version.substring(0, majorDotIdx));
Integer minor = Integer.valueOf(version.substring(majorDotIdx + 1, minorDotIdx));
Integer patch = Integer.valueOf(version.substring(minorDotIdx + 1, patchEndIdx));

return new SemanticVersion(major, minor, patch, version.substring(patchEndIdx, extEndIdx), version);
} catch (Throwable ex) {
return createInvalid(version);
}
}

/**
* Returns implementation version of the package for given class.
*
* @param clazz - class to get package version of.
* @return parsed {@link SemanticVersion} or invalid one.
*/
private static SemanticVersion getPackageVersion(Class<?> clazz) {
Objects.requireNonNull(clazz, "'clazz' cannot be null.");
if (clazz.getPackage() == null) {
return createInvalid();
}

String versionStr = clazz.getPackage().getImplementationVersion();
if (versionStr == null) {
return createInvalid();
}

return parse(versionStr);
}

private final int major;
private final int minor;
private final int patch;
private final String prerelease;
private final String versionString;

/**
* Creates invalid semantic version.
* @return instance of invalid semantic version.
*/
public static SemanticVersion createInvalid() {
return createInvalid(UNKNOWN_VERSION);
}

/**
* Creates invalid semantic version.
*/
private static SemanticVersion createInvalid(String version) {
return new SemanticVersion(-1, -1, -1, null, version);
}

/**
* Creates semantic version.
*
* @param major major version.
* @param minor minor version.
* @param patch patch version.
* @param prerelease extensions (including '-' or '+' separator after patch).
* @param versionString full version string.
*/
SemanticVersion(int major, int minor, int patch, String prerelease, String versionString) {
Objects.requireNonNull(versionString, "'versionString' cannot be null.");
this.major = major;
this.minor = minor;
this.patch = patch;
this.prerelease = prerelease;
this.versionString = versionString;
}

/**
* Returns full version string that was used to create this {@code SemanticVersion}
*
* @return original version string.
*/
public String getVersionString() {
return versionString;
}

/**
* Returns major version component or -1 for invalid version.
*
* @return major version.
*/
public int getMajorVersion() { return major; }

/**
* {@inheritDoc}
*/
@Override
public int compareTo(SemanticVersion other) {
if (this == other) {
return 0;
}

if (other == null) {
return -1;
}

if (major != other.major) {
return major > other.major ? 1 : -1;
}

if (minor != other.minor) {
return minor > other.minor ? 1 : -1;
}

if (patch != other.patch) {
return patch > other.patch ? 1 : -1;
}

if (isStringNullOrEmpty(prerelease)) {
return isStringNullOrEmpty(other.prerelease) ? 0 : 1;
}

if (isStringNullOrEmpty(other.prerelease)) {
return -1;
}

return prerelease.compareTo(other.prerelease);
}

/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}

if (!(other instanceof SemanticVersion)) {
return false;
}

SemanticVersion otherVer = (SemanticVersion) other;

return versionString.equals(otherVer.versionString);
}

/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return versionString.hashCode();
}

/**
* Returns flag indicating if version is valid.
*
* @return true if version is valid, false otherwise.
*/
public boolean isValid() {
return this.major >= 0;
}

private static boolean isStringNullOrEmpty(String str) {
return str == null || str.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.azure.core.implementation.jackson;

import com.azure.core.implementation.SemanticVersion;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;

Expand All @@ -23,14 +24,16 @@ final class JacksonVersion {
private static final String JSR310_PACKAGE_NAME = "jackson-datatype-jsr310";

private static final SemanticVersion MIN_SUPPORTED_VERSION = SemanticVersion.parse("2.10.0");
private static final SemanticVersion MAX_SUPPORTED_VERSION = SemanticVersion.parse("2.13.0-rc1");
private static final SemanticVersion MAX_SUPPORTED_VERSION = SemanticVersion.parse("2.12.4");

private static final String AZURE_CORE_PROPERTIES_NAME = "azure-core.properties";
private static final String AZURE_CORE_PROPERTIES_VERSION_KEY = "version";

private static final String AZURE_CORE_VERSION = CoreUtils
.getProperties("azure-core.properties")
.getOrDefault("version", SemanticVersion.UNKNOWN_VERSION);
.getProperties(AZURE_CORE_PROPERTIES_NAME)
.getOrDefault(AZURE_CORE_PROPERTIES_VERSION_KEY, SemanticVersion.UNKNOWN_VERSION);

private final String helpString;
private final boolean criticalErrorDetected;
private final ClientLogger logger = new ClientLogger(JacksonVersion.class);

private JacksonVersion() {
Expand All @@ -39,11 +42,11 @@ private JacksonVersion() {
databindVersion = getVersion("com.fasterxml.jackson.databind.ObjectMapper");
xmlVersion = getVersion("com.fasterxml.jackson.dataformat.xml.XmlMapper");
jsr310Version = getVersion("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
criticalErrorDetected = checkVersion(annotationsVersion, ANNOTATIONS_PACKAGE_NAME)
| checkVersion(coreVersion, CORE_PACKAGE_NAME)
| checkVersion(databindVersion, DATABIND_PACKAGE_NAME)
| checkVersion(xmlVersion, XML_PACKAGE_NAME)
| checkVersion(jsr310Version, JSR310_PACKAGE_NAME);
checkVersion(annotationsVersion, ANNOTATIONS_PACKAGE_NAME);
checkVersion(coreVersion, CORE_PACKAGE_NAME);
checkVersion(databindVersion, DATABIND_PACKAGE_NAME);
checkVersion(xmlVersion, XML_PACKAGE_NAME);
checkVersion(jsr310Version, JSR310_PACKAGE_NAME);
helpString = formatHelpString();
logger.info(helpString);
}
Expand All @@ -70,39 +73,49 @@ public static synchronized JacksonVersion getInstance() {
return instance;
}

private boolean checkVersion(SemanticVersion version, String packageName) {
private void checkVersion(SemanticVersion version, String packageName) {
if (!version.isValid()) {
logger.warning("Could not find version of '{}'.", packageName);
return false;
}

if (version.compareTo(MIN_SUPPORTED_VERSION) < 0) {
logger.error("Version '{}' of package '{}' is not supported (too old), please upgrade.", version.getVersionString(), packageName);
return true;
}

if (version.getMajorVersion() > MAX_SUPPORTED_VERSION.getMajorVersion()) {
logger.error("Major version '{}' of package '{}' is newer than latest supported version - '{}'.",
version.getVersionString(),
packageName,
MAX_SUPPORTED_VERSION.getVersionString());
return true;
}

return false;
}

private String formatHelpString() {
// TODO(limolkova): add link to troubleshooting docs
return new StringBuilder()
.append("Package versions: ")
.append(String.format("%s=%s, ", ANNOTATIONS_PACKAGE_NAME, annotationsVersion.getVersionString()))
.append(String.format("%s=%s, ", CORE_PACKAGE_NAME, coreVersion.getVersionString()))
.append(String.format("%s=%s, ", DATABIND_PACKAGE_NAME, databindVersion.getVersionString()))
.append(String.format("%s=%s, ", XML_PACKAGE_NAME, xmlVersion.getVersionString()))
.append(String.format("%s=%s, ", JSR310_PACKAGE_NAME, jsr310Version.getVersionString()))
.append(String.format("azure-core=%s, ", AZURE_CORE_VERSION))
.append(String.format("critical errors found: %b", criticalErrorDetected))
.append(ANNOTATIONS_PACKAGE_NAME)
.append("=")
.append(annotationsVersion.getVersionString())
.append(", ")
.append(CORE_PACKAGE_NAME)
.append("=")
.append(coreVersion.getVersionString())
.append(", ")
.append(DATABIND_PACKAGE_NAME)
.append("=")
.append(databindVersion.getVersionString())
.append(", ")
.append(XML_PACKAGE_NAME)
.append("=")
.append(xmlVersion.getVersionString())
.append(", ")
.append(JSR310_PACKAGE_NAME)
.append("=")
.append(jsr310Version.getVersionString())
.append(", ")
.append("azure-core=")
.append(AZURE_CORE_VERSION)
.toString();
}
}
Loading

0 comments on commit dc3c3a4

Please sign in to comment.