blob: 0ef56d5281766cf770c608982577709bc1faca7a [file] [log] [blame]
/*
* 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.
*/
#include "celix_version.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "celix_utils.h"
#include "celix_convert_utils_private.h"
#include "celix_convert_utils.h"
#include "celix_err.h"
#include "celix_errno.h"
#include "version_private.h"
static const char* const CELIX_VERSION_EMPTY_QUALIFIER = "";
celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier) {
if (major < 0 || minor < 0 || micro < 0) {
errno = EINVAL;
celix_err_push("Invalid version number. Major, minor and micro must be >= 0");
return NULL;
}
if (qualifier == NULL) {
qualifier = CELIX_VERSION_EMPTY_QUALIFIER;
}
size_t qualifierLen = strlen(qualifier);
for (int i = 0; i < qualifierLen; i++) {
char ch = qualifier[i];
if (('A' <= ch) && (ch <= 'Z')) {
continue;
}
if (('a' <= ch) && (ch <= 'z')) {
continue;
}
if (('0' <= ch) && (ch <= '9')) {
continue;
}
if ((ch == '_') || (ch == '-')) {
continue;
}
errno = EINVAL;
celix_err_push("Invalid version qualifier. Characters must be [A-Za-z0-9_-]");
return NULL;
}
celix_version_t* version = calloc(1, sizeof(*version));
if (version) {
version->major = major;
version->minor = minor;
version->micro = micro;
if (qualifierLen == 0) {
version->qualifier = (char*)CELIX_VERSION_EMPTY_QUALIFIER;
} else {
version->qualifier = celix_utils_strdup(qualifier);
}
if (!version->qualifier) {
celix_version_destroy(version);
version = NULL;
}
}
if (!version) {
celix_err_push("Failed to allocate memory for celix_version_create");
}
return version;
}
void celix_version_destroy(celix_version_t* version) {
if (version != NULL) {
if (version->qualifier != CELIX_VERSION_EMPTY_QUALIFIER) {
free(version->qualifier);
}
free(version);
}
}
celix_version_t* celix_version_copy(const celix_version_t* version) {
if (!version) {
return NULL;
}
return celix_version_create(version->major, version->minor, version->micro, version->qualifier);
}
celix_version_t* celix_version_createVersionFromString(const char *versionStr) {
celix_version_t* version;
celix_status_t status = celix_version_parse(versionStr, &version);
(void)status; //silently ignore status
return version;
}
static celix_status_t
celix_version_parseInternal(const char* versionStr, bool logParseError, celix_version_t** version) {
*version = NULL;
if (versionStr == NULL) {
return CELIX_ILLEGAL_ARGUMENT;
}
int versionsParts[3] = {0, 0, 0};
int count = 0;
const char* token = versionStr;
const char* qualifier = NULL;
while (token != NULL && count < 3) {
char* endPtr = NULL;
errno = 0;
long l = strtol(token, &endPtr, 10);
if (errno != 0 || token == endPtr || l < 0 || l >= INT_MAX) {
if (logParseError) {
celix_err_pushf("Invalid version part %d. Input str: %s", count, versionStr);
}
return CELIX_ILLEGAL_ARGUMENT;
}
versionsParts[count++] = (int)l;
if (*endPtr == '.') {
token = endPtr + 1;
} else if (celix_utils_isEndptrEndOfStringOrOnlyContainsWhitespaces(endPtr)) {
token = NULL;
} else {
if (logParseError) {
celix_err_pushf("Invalid trailing string `%s`. Input str: %s", endPtr, versionStr);
}
return CELIX_ILLEGAL_ARGUMENT;
}
}
if (token != NULL) {
qualifier = token;
}
*version = celix_version_create(versionsParts[0], versionsParts[1], versionsParts[2], qualifier);
return *version ? CELIX_SUCCESS : (errno == EINVAL ? CELIX_ILLEGAL_ARGUMENT : CELIX_ENOMEM);
}
celix_status_t celix_version_parse(const char* versionStr, celix_version_t** version) {
return celix_version_parseInternal(versionStr, true, version);
}
celix_status_t celix_version_tryParse(const char* versionStr, celix_version_t** version) {
return celix_version_parseInternal(versionStr, false, version);
}
celix_version_t* celix_version_createEmptyVersion() {
return celix_version_create(0, 0, 0, NULL);
}
int celix_version_getMajor(const celix_version_t* version) {
return version->major;
}
int celix_version_getMinor(const celix_version_t* version) {
return version->minor;
}
int celix_version_getMicro(const celix_version_t* version) {
return version->micro;
}
const char* celix_version_getQualifier(const celix_version_t* version) {
return version->qualifier;
}
int celix_version_compareTo(const celix_version_t* version, const celix_version_t* compare) {
int result;
if (compare == version) {
result = 0;
} else {
int res = version->major - compare->major;
if (res != 0) {
result = res;
} else {
res = version->minor - compare->minor;
if (res != 0) {
result = res;
} else {
res = version->micro - compare->micro;
if (res != 0) {
result = res;
} else {
// by class invariant qualifier is never null
result = strcmp(version->qualifier, compare->qualifier);
}
}
}
}
return result;
}
char* celix_version_toString(const celix_version_t* version) {
char* string = NULL;
int rc;
if (strlen(version->qualifier) > 0) {
rc = asprintf(&string,"%d.%d.%d.%s", version->major, version->minor, version->micro, version->qualifier);
} else {
rc = asprintf(&string, "%d.%d.%d", version->major, version->minor, version->micro);
}
if (rc < 0) {
celix_err_push("Failed to allocate memory for celix_version_toString");
return NULL;
}
return string;
}
bool celix_version_fillString(const celix_version_t* version, char *str, size_t strLen) {
int written;
if (strnlen(version->qualifier, 1) > 0) {
written = snprintf(str, strLen, "%d.%d.%d.%s", version->major, version->minor, version->micro, version->qualifier);
} else {
written = snprintf(str, strLen, "%d.%d.%d", version->major, version->minor, version->micro);
}
return written >= 0 && written < strLen;
}
bool celix_version_isCompatible(const celix_version_t* user, const celix_version_t* provider) {
if (user == NULL && provider == NULL) {
return true;
} else if (user == NULL || provider == NULL) {
return false;
} else {
return celix_version_isUserCompatible(user, provider->major, provider->minor);
}
}
bool celix_version_isUserCompatible(const celix_version_t* user, int providerMajorVersionPart, int provideMinorVersionPart) {
bool isCompatible = false;
if (providerMajorVersionPart == user->major) {
isCompatible = (provideMinorVersionPart >= user->minor);
}
return isCompatible;
}
unsigned int celix_version_hash(const celix_version_t* version) {
unsigned int h = 0;
h = 31 * 17;
h = 31 * h + (unsigned int)version->major;
h = 31 * h + (unsigned int)version->minor;
h = 31 * h + (unsigned int)version->micro;
h = 31 * h + celix_utils_stringHash(version->qualifier);
return h;
}
int celix_version_compareToMajorMinor(const celix_version_t* version, int majorVersionPart, int minorVersionPart) {
int result = version->major - majorVersionPart;
if (result == 0) {
result = version->minor - minorVersionPart;
}
return result;
}