1/*
2* Copyright 2003, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
3* Copyright 2010, Adrien Destugues <pulkomandy@pulkomandy.ath.cx>
4* Distributed under the terms of the MIT License.
5*/
6
7
8#include <unicode/uversion.h>
9#include <Collator.h>
10
11#include <ctype.h>
12#include <stdlib.h>
13
14#include <new>
15#include <typeinfo>
16
17#include <UnicodeChar.h>
18#include <String.h>
19#include <Message.h>
20
21#include <unicode/coll.h>
22#include <unicode/tblcoll.h>
23
24
25BCollator::BCollator()
26	:
27	fIgnorePunctuation(true)
28{
29	// TODO: the collator construction will have to change; the default
30	//	collator should be constructed by the Locale/LocaleRoster, so we
31	//	only need a constructor where you specify all details
32
33	UErrorCode error = U_ZERO_ERROR;
34	fICUCollator = Collator::createInstance(error);
35	SetStrength(B_COLLATE_TERTIARY);
36}
37
38
39BCollator::BCollator(const char* locale, int8 strength, bool ignorePunctuation)
40	:
41	fIgnorePunctuation(ignorePunctuation)
42{
43	UErrorCode error = U_ZERO_ERROR;
44	fICUCollator = Collator::createInstance(locale, error);
45	SetStrength(strength);
46}
47
48
49BCollator::BCollator(BMessage* archive)
50	:
51	BArchivable(archive),
52	fICUCollator(NULL),
53	fIgnorePunctuation(true)
54{
55	archive->FindBool("loc:punctuation", &fIgnorePunctuation);
56
57	UErrorCode error = U_ZERO_ERROR;
58	RuleBasedCollator* fallbackICUCollator
59		= static_cast<RuleBasedCollator*>(Collator::createInstance(error));
60
61	ssize_t size;
62	const void* buffer = NULL;
63	if (archive->FindData("loc:collator", B_RAW_TYPE, &buffer, &size) == B_OK) {
64		fICUCollator = new RuleBasedCollator((const uint8_t*)buffer, (int)size,
65			fallbackICUCollator, error);
66		if (fICUCollator == NULL) {
67			fICUCollator = fallbackICUCollator;
68				// Unarchiving failed, so we revert to the fallback collator
69				// TODO: when can this happen, can it be avoided?
70		}
71	}
72}
73
74
75BCollator::BCollator(const BCollator& other)
76	:
77	fICUCollator(NULL)
78{
79	*this = other;
80}
81
82
83BCollator::~BCollator()
84{
85	delete fICUCollator;
86}
87
88
89BCollator& BCollator::operator=(const BCollator& source)
90{
91	if (&source != this) {
92		delete fICUCollator;
93
94		fICUCollator = source.fICUCollator != NULL
95			? source.fICUCollator->clone()
96			: NULL;
97		fIgnorePunctuation = source.fIgnorePunctuation;
98	}
99
100	return *this;
101}
102
103
104void
105BCollator::SetIgnorePunctuation(bool ignore)
106{
107	fIgnorePunctuation = ignore;
108}
109
110
111bool
112BCollator::IgnorePunctuation() const
113{
114	return fIgnorePunctuation;
115}
116
117
118status_t
119BCollator::SetNumericSorting(bool enable)
120{
121	UErrorCode error = U_ZERO_ERROR;
122	fICUCollator->setAttribute(UCOL_NUMERIC_COLLATION,
123		enable ? UCOL_ON : UCOL_OFF, error);
124
125	return error == U_ZERO_ERROR ? B_OK : B_ERROR;
126}
127
128
129status_t
130BCollator::GetSortKey(const char* string, BString* key) const
131{
132	// TODO : handle fIgnorePunctuation
133
134	int length = strlen(string);
135
136	uint8_t* buffer = (uint8_t*)malloc(length * 2);
137		// According to ICU documentation this should be enough in "most cases"
138	if (buffer == NULL)
139		return B_NO_MEMORY;
140
141	UErrorCode error = U_ZERO_ERROR;
142	int requiredSize = fICUCollator->getSortKey(UnicodeString(string, length,
143		NULL, error), buffer, length * 2);
144	if (requiredSize > length * 2) {
145		uint8_t* tmpBuffer = (uint8_t*)realloc(buffer, requiredSize);
146		if (tmpBuffer == NULL) {
147			free(buffer);
148			buffer = NULL;
149			return B_NO_MEMORY;
150		} else {
151			buffer = tmpBuffer;
152		}
153
154		error = U_ZERO_ERROR;
155		fICUCollator->getSortKey(UnicodeString(string, length, NULL, error),
156			buffer,	requiredSize);
157	}
158
159	key->SetTo((char*)buffer);
160	free(buffer);
161
162	if (error == U_ZERO_ERROR)
163		return B_OK;
164
165	return B_ERROR;
166}
167
168
169int
170BCollator::Compare(const char* s1, const char* s2) const
171{
172	// TODO : handle fIgnorePunctuation
173
174	UErrorCode error = U_ZERO_ERROR;
175	return fICUCollator->compare(s1, s2, error);
176}
177
178
179status_t
180BCollator::Archive(BMessage* archive, bool deep) const
181{
182	status_t status = BArchivable::Archive(archive, deep);
183	if (status < B_OK)
184		return status;
185
186	if (status == B_OK)
187		status = archive->AddBool("loc:punctuation", fIgnorePunctuation);
188
189	UErrorCode error = U_ZERO_ERROR;
190	int size = static_cast<RuleBasedCollator*>(fICUCollator)->cloneBinary(NULL,
191		0, error);
192		// This WILL fail with U_BUFFER_OVERFLOW_ERROR. But we get the needed
193		// size.
194	error = U_ZERO_ERROR;
195	uint8_t* buffer = (uint8_t*)malloc(size);
196	static_cast<RuleBasedCollator*>(fICUCollator)->cloneBinary(buffer, size,
197		error);
198
199	if (status == B_OK && error == U_ZERO_ERROR)
200		status = archive->AddData("loc:collator", B_RAW_TYPE, buffer, size);
201	free(buffer);
202
203	if (error == U_ZERO_ERROR)
204		return status;
205	return B_ERROR;
206}
207
208
209BArchivable*
210BCollator::Instantiate(BMessage* archive)
211{
212	if (validate_instantiation(archive, "BCollator"))
213		return new(std::nothrow) BCollator(archive);
214
215	return NULL;
216}
217
218
219status_t
220BCollator::SetStrength(int8 strength) const
221{
222	if (strength == B_COLLATE_DEFAULT)
223		strength = B_COLLATE_TERTIARY;
224
225	Collator::ECollationStrength icuStrength;
226	switch (strength) {
227		case B_COLLATE_PRIMARY:
228			icuStrength = Collator::PRIMARY;
229			break;
230		case B_COLLATE_SECONDARY:
231			icuStrength = Collator::SECONDARY;
232			break;
233		case B_COLLATE_TERTIARY:
234		default:
235			icuStrength = Collator::TERTIARY;
236			break;
237		case B_COLLATE_QUATERNARY:
238			icuStrength = Collator::QUATERNARY;
239			break;
240		case B_COLLATE_IDENTICAL:
241			icuStrength = Collator::IDENTICAL;
242			break;
243	}
244	fICUCollator->setStrength(icuStrength);
245
246	return B_OK;
247}
248