⛏️ index : haiku.git

/*
 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "Version.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <algorithm>
#include <new>

#include <NaturalCompare.h>

#include "DebugSupport.h"


static const char* const kVersionPartPlaceholder = "_";


static int
compare_version_part(const String& a, const String& b)
{
	if (a.IsEmpty())
		return b.IsEmpty() ? 0 : -1;
	if (b.IsEmpty())
		return 1;

	return BPrivate::NaturalCompare(a, b);
}


Version::Version()
	:
	fMajor(),
	fMinor(),
	fMicro(),
	fPreRelease(),
	fRevision(0)
{
}


Version::~Version()
{
}


status_t
Version::Init(const char* major, const char* minor, const char* micro,
	const char* preRelease, uint32 revision)
{
	if (major != NULL) {
		if (!fMajor.SetTo(major))
			return B_NO_MEMORY;
	}

	if (minor != NULL) {
		if (!fMinor.SetTo(minor))
			return B_NO_MEMORY;
	}

	if (micro != NULL) {
		if (!fMicro.SetTo(micro))
			return B_NO_MEMORY;
	}

	if (preRelease != NULL) {
		if (!fPreRelease.SetTo(preRelease))
			return B_NO_MEMORY;
	}

	fRevision = revision;

	return B_OK;
}


/*static*/ status_t
Version::Create(const char* major, const char* minor, const char* micro,
	const char* preRelease, uint32 revision, Version*& _version)
{
	Version* version = new(std::nothrow) Version;
	if (version == NULL)
		return B_NO_MEMORY;

	status_t error = version->Init(major, minor, micro, preRelease, revision);
	if (error != B_OK) {
		delete version;
		return error;
	}

	_version = version;
	return B_OK;
}


int
Version::Compare(const Version& other) const
{
	int cmp = compare_version_part(fMajor, other.fMajor);
	if (cmp != 0)
		return cmp;

	cmp = compare_version_part(fMinor, other.fMinor);
	if (cmp != 0)
		return cmp;

	cmp = compare_version_part(fMicro, other.fMicro);
	if (cmp != 0)
		return cmp;

	// The pre-version works differently: The empty string is greater than any
	// non-empty string (e.g. "R1" is newer than "R1-rc2"). So we catch the
	// empty string cases first.
	if (fPreRelease.IsEmpty()) {
		if (!other.fPreRelease.IsEmpty())
			return 1;
	} else if (other.fPreRelease.IsEmpty()) {
		return -1;
	} else {
		// both are non-null -- compare normally
		cmp = BPrivate::NaturalCompare(fPreRelease, other.fPreRelease);
		if (cmp != 0)
			return cmp;
	}

	return fRevision == other.fRevision
		? 0 : (fRevision < other.fRevision ? -1 : 1);
}


bool
Version::Compare(BPackageResolvableOperator op,
	const Version& other) const
{
	int cmp = Compare(other);

	switch (op) {
		case B_PACKAGE_RESOLVABLE_OP_LESS:
			return cmp < 0;
		case B_PACKAGE_RESOLVABLE_OP_LESS_EQUAL:
			return cmp <= 0;
		case B_PACKAGE_RESOLVABLE_OP_EQUAL:
			return cmp == 0;
		case B_PACKAGE_RESOLVABLE_OP_NOT_EQUAL:
			return cmp != 0;
		case B_PACKAGE_RESOLVABLE_OP_GREATER_EQUAL:
			return cmp >= 0;
		case B_PACKAGE_RESOLVABLE_OP_GREATER:
			return cmp > 0;
		default:
			ERROR("packagefs: Version::Compare(): Invalid operator %d\n", op);
			return false;
	}
}


size_t
Version::ToString(char* buffer, size_t bufferSize) const
{
	// We need to normalize the version string somewhat. If a subpart is given,
	// make sure that also the superparts are defined, using a placeholder. This
	// avoids clashes, e.g. if one version defines major and minor and one only
	// major and micro. In principle that should not be necessary, though. Valid
	// packages should have valid versions, which means that the existence of a
	// subpart implies the existence of all superparts.
	const char* major = fMajor;
	const char* minor = fMinor;
	const char* micro = fMicro;

	if (micro[0] != '\0' && minor[0] == '\0')
		minor = kVersionPartPlaceholder;
	if (minor[0] != '\0' && major[0] == '\0')
		major = kVersionPartPlaceholder;

	size_t size = strlcpy(buffer, major, bufferSize);

	if (minor[0] != '\0') {
		size_t offset = std::min(bufferSize, size);
		size += snprintf(buffer + offset, bufferSize - offset, ".%s", minor);
	}

	if (micro[0] != '\0') {
		size_t offset = std::min(bufferSize, size);
		size += snprintf(buffer + offset, bufferSize - offset, ".%s", micro);
	}

	if (fPreRelease[0] != '\0') {
		size_t offset = std::min(bufferSize, size);
		size += snprintf(buffer + offset, bufferSize - offset, "~%s",
			fPreRelease.Data());
	}

	if (fRevision != 0) {
		size_t offset = std::min(bufferSize, size);
		size += snprintf(buffer + offset, bufferSize - offset, "-%" B_PRIu32,
			fRevision);
	}

	return size;
}