xref: /haiku/src/add-ons/kernel/file_systems/packagefs/util/Version.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
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 
21 static const char* const kVersionPartPlaceholder = "_";
22 
23 
24 static int
25 compare_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 
36 Version::Version()
37 	:
38 	fMajor(),
39 	fMinor(),
40 	fMicro(),
41 	fPreRelease(),
42 	fRevision(0)
43 {
44 }
45 
46 
47 Version::~Version()
48 {
49 }
50 
51 
52 status_t
53 Version::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
83 Version::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 
101 int
102 Version::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 
136 bool
137 Version::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 
162 size_t
163 Version::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