1/*
2 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "Version.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include <algorithm>
14#include <new>
15
16#include <NaturalCompare.h>
17
18#include "DebugSupport.h"
19
20
21static const char* const kVersionPartPlaceholder = "_";
22
23
24static int
25compare_version_part(const String& a, const String& b)
26{
27	if (a.IsEmpty())
28		return b.IsEmpty() ? 0 : -1;
29	if (b.IsEmpty())
30		return 1;
31
32	return BPrivate::NaturalCompare(a, b);
33}
34
35
36Version::Version()
37	:
38	fMajor(),
39	fMinor(),
40	fMicro(),
41	fPreRelease(),
42	fRevision(0)
43{
44}
45
46
47Version::~Version()
48{
49}
50
51
52status_t
53Version::Init(const char* major, const char* minor, const char* micro,
54	const char* preRelease, uint32 revision)
55{
56	if (major != NULL) {
57		if (!fMajor.SetTo(major))
58			return B_NO_MEMORY;
59	}
60
61	if (minor != NULL) {
62		if (!fMinor.SetTo(minor))
63			return B_NO_MEMORY;
64	}
65
66	if (micro != NULL) {
67		if (!fMicro.SetTo(micro))
68			return B_NO_MEMORY;
69	}
70
71	if (preRelease != NULL) {
72		if (!fPreRelease.SetTo(preRelease))
73			return B_NO_MEMORY;
74	}
75
76	fRevision = revision;
77
78	return B_OK;
79}
80
81
82/*static*/ status_t
83Version::Create(const char* major, const char* minor, const char* micro,
84	const char* preRelease, uint32 revision, Version*& _version)
85{
86	Version* version = new(std::nothrow) Version;
87	if (version == NULL)
88		return B_NO_MEMORY;
89
90	status_t error = version->Init(major, minor, micro, preRelease, revision);
91	if (error != B_OK) {
92		delete version;
93		return error;
94	}
95
96	_version = version;
97	return B_OK;
98}
99
100
101int
102Version::Compare(const Version& other) const
103{
104	int cmp = compare_version_part(fMajor, other.fMajor);
105	if (cmp != 0)
106		return cmp;
107
108	cmp = compare_version_part(fMinor, other.fMinor);
109	if (cmp != 0)
110		return cmp;
111
112	cmp = compare_version_part(fMicro, other.fMicro);
113	if (cmp != 0)
114		return cmp;
115
116	// The pre-version works differently: The empty string is greater than any
117	// non-empty string (e.g. "R1" is newer than "R1-rc2"). So we catch the
118	// empty string cases first.
119	if (fPreRelease.IsEmpty()) {
120		if (!other.fPreRelease.IsEmpty())
121			return 1;
122	} else if (other.fPreRelease.IsEmpty()) {
123		return -1;
124	} else {
125		// both are non-null -- compare normally
126		cmp = BPrivate::NaturalCompare(fPreRelease, other.fPreRelease);
127		if (cmp != 0)
128			return cmp;
129	}
130
131	return fRevision == other.fRevision
132		? 0 : (fRevision < other.fRevision ? -1 : 1);
133}
134
135
136bool
137Version::Compare(BPackageResolvableOperator op,
138	const Version& other) const
139{
140	int cmp = Compare(other);
141
142	switch (op) {
143		case B_PACKAGE_RESOLVABLE_OP_LESS:
144			return cmp < 0;
145		case B_PACKAGE_RESOLVABLE_OP_LESS_EQUAL:
146			return cmp <= 0;
147		case B_PACKAGE_RESOLVABLE_OP_EQUAL:
148			return cmp == 0;
149		case B_PACKAGE_RESOLVABLE_OP_NOT_EQUAL:
150			return cmp != 0;
151		case B_PACKAGE_RESOLVABLE_OP_GREATER_EQUAL:
152			return cmp >= 0;
153		case B_PACKAGE_RESOLVABLE_OP_GREATER:
154			return cmp > 0;
155		default:
156			ERROR("packagefs: Version::Compare(): Invalid operator %d\n", op);
157			return false;
158	}
159}
160
161
162size_t
163Version::ToString(char* buffer, size_t bufferSize) const
164{
165	// We need to normalize the version string somewhat. If a subpart is given,
166	// make sure that also the superparts are defined, using a placeholder. This
167	// avoids clashes, e.g. if one version defines major and minor and one only
168	// major and micro. In principle that should not be necessary, though. Valid
169	// packages should have valid versions, which means that the existence of a
170	// subpart implies the existence of all superparts.
171	const char* major = fMajor;
172	const char* minor = fMinor;
173	const char* micro = fMicro;
174
175	if (micro[0] != '\0' && minor[0] == '\0')
176		minor = kVersionPartPlaceholder;
177	if (minor[0] != '\0' && major[0] == '\0')
178		major = kVersionPartPlaceholder;
179
180	size_t size = strlcpy(buffer, major, bufferSize);
181
182	if (minor[0] != '\0') {
183		size_t offset = std::min(bufferSize, size);
184		size += snprintf(buffer + offset, bufferSize - offset, ".%s", minor);
185	}
186
187	if (micro[0] != '\0') {
188		size_t offset = std::min(bufferSize, size);
189		size += snprintf(buffer + offset, bufferSize - offset, ".%s", micro);
190	}
191
192	if (fPreRelease[0] != '\0') {
193		size_t offset = std::min(bufferSize, size);
194		size += snprintf(buffer + offset, bufferSize - offset, "~%s",
195			fPreRelease.Data());
196	}
197
198	if (fRevision != 0) {
199		size_t offset = std::min(bufferSize, size);
200		size += snprintf(buffer + offset, bufferSize - offset, "-%" B_PRIu32,
201			fRevision);
202	}
203
204	return size;
205}
206