blob: cbe0394930c723d5c97b026a5dcb74b3c542c160 [file] [log] [blame]
/*
******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and
* others. All Rights Reserved.
******************************************************************************
*/
/**
* File tblcoll.cpp
*
* Created by: Helena Shih
*
* Modification History:
*
* Date Name Description
* 2/5/97 aliu Added streamIn and streamOut methods. Added
* constructor which reads RuleBasedCollator object from
* a binary file. Added writeToFile method which streams
* RuleBasedCollator out to a binary file. The streamIn
* and streamOut methods use istream and ostream objects
* in binary mode.
* 2/11/97 aliu Moved declarations out of for loop initializer.
* Added Mac compatibility #ifdef for ios::nocreate.
* 2/12/97 aliu Modified to use TableCollationData sub-object to
* hold invariant data.
* 2/13/97 aliu Moved several methods into this class from Collation.
* Added a private RuleBasedCollator(Locale&) constructor,
* to be used by Collator::getInstance(). General
* clean up. Made use of UErrorCode variables consistent.
* 2/20/97 helena Added clone, operator==, operator!=, operator=, and copy
* constructor and getDynamicClassID.
* 3/5/97 aliu Changed compaction cycle to improve performance. We
* use the maximum allowable value which is kBlockCount.
* Modified getRules() to load rules dynamically. Changed
* constructFromFile() call to accomodate this (added
* parameter to specify whether binary loading is to
* take place).
* 05/06/97 helena Added memory allocation error check.
* 6/20/97 helena Java class name change.
* 6/23/97 helena Adding comments to make code more readable.
* 09/03/97 helena Added createCollationKeyValues().
* 06/26/98 erm Changes for CollationKeys using byte arrays.
* 08/10/98 erm Synched with 1.2 version of RuleBasedCollator.java
* 04/23/99 stephen Removed EDecompositionMode, merged with
* Normalizer::EMode
* 06/14/99 stephen Removed kResourceBundleSuffix
* 06/22/99 stephen Fixed logic in constructFromFile() since .ctx
* files are no longer used.
* 11/02/99 helena Collator performance enhancements. Special case
* for NO_OP situations.
* 11/17/99 srl More performance enhancements. Inlined some internal functions.
* 12/15/99 aliu Update to support Thai collation. Move NormalizerIterator
* to implementation file.
* 01/29/01 synwee Modified into a C++ wrapper calling C APIs (ucol.h)
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_COLLATION
#include "unicode/tblcoll.h"
#include "unicode/coleitr.h"
#include "unicode/ures.h"
#include "unicode/uset.h"
#include "ucol_imp.h"
#include "uresimp.h"
#include "uhash.h"
#include "cmemory.h"
#include "cstring.h"
#include "putilimp.h"
#include "ustr_imp.h"
/* public RuleBasedCollator constructor ---------------------------------- */
U_NAMESPACE_BEGIN
/**
* Copy constructor, aliasing, not write-through
*/
RuleBasedCollator::RuleBasedCollator(const RuleBasedCollator& that)
: Collator(that)
, dataIsOwned(FALSE)
, isWriteThroughAlias(FALSE)
, ucollator(NULL)
{
RuleBasedCollator::operator=(that);
}
RuleBasedCollator::RuleBasedCollator(const UnicodeString& rules,
UErrorCode& status) :
dataIsOwned(FALSE)
{
construct(rules,
UCOL_DEFAULT_STRENGTH,
UCOL_DEFAULT,
status);
}
RuleBasedCollator::RuleBasedCollator(const UnicodeString& rules,
ECollationStrength collationStrength,
UErrorCode& status) : dataIsOwned(FALSE)
{
construct(rules,
(UColAttributeValue)collationStrength,
UCOL_DEFAULT,
status);
}
RuleBasedCollator::RuleBasedCollator(const UnicodeString& rules,
UColAttributeValue decompositionMode,
UErrorCode& status) :
dataIsOwned(FALSE)
{
construct(rules,
UCOL_DEFAULT_STRENGTH,
decompositionMode,
status);
}
RuleBasedCollator::RuleBasedCollator(const UnicodeString& rules,
ECollationStrength collationStrength,
UColAttributeValue decompositionMode,
UErrorCode& status) : dataIsOwned(FALSE)
{
construct(rules,
(UColAttributeValue)collationStrength,
decompositionMode,
status);
}
RuleBasedCollator::RuleBasedCollator(const uint8_t *bin, int32_t length,
const RuleBasedCollator *base,
UErrorCode &status) :
dataIsOwned(TRUE),
isWriteThroughAlias(FALSE)
{
ucollator = ucol_openBinary(bin, length, base->ucollator, &status);
}
void
RuleBasedCollator::setRuleStringFromCollator()
{
int32_t length;
const UChar *r = ucol_getRules(ucollator, &length);
if (r && length > 0) {
// alias the rules string
urulestring.setTo(TRUE, r, length);
}
else {
urulestring.truncate(0); // Clear string.
}
}
// not aliasing, not write-through
void
RuleBasedCollator::construct(const UnicodeString& rules,
UColAttributeValue collationStrength,
UColAttributeValue decompositionMode,
UErrorCode& status)
{
ucollator = ucol_openRules(rules.getBuffer(), rules.length(),
decompositionMode, collationStrength,
NULL, &status);
dataIsOwned = TRUE; // since we own a collator now, we need to get rid of it
isWriteThroughAlias = FALSE;
if(ucollator == NULL) {
if(U_SUCCESS(status)) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return; // Failure
}
setRuleStringFromCollator();
}
/* RuleBasedCollator public destructor ----------------------------------- */
RuleBasedCollator::~RuleBasedCollator()
{
if (dataIsOwned)
{
ucol_close(ucollator);
}
ucollator = 0;
}
/* RuleBaseCollator public methods --------------------------------------- */
UBool RuleBasedCollator::operator==(const Collator& that) const
{
/* only checks for address equals here */
if (this == &that) {
return TRUE;
}
if (!Collator::operator==(that)) {
return FALSE; /* not the same class */
}
RuleBasedCollator& thatAlias = (RuleBasedCollator&)that;
return ucol_equals(this->ucollator, thatAlias.ucollator);
}
// aliasing, not write-through
RuleBasedCollator& RuleBasedCollator::operator=(const RuleBasedCollator& that)
{
if (this == &that) { return *this; }
UErrorCode intStatus = U_ZERO_ERROR;
int32_t buffersize = U_COL_SAFECLONE_BUFFERSIZE;
UCollator *ucol = ucol_safeClone(that.ucollator, NULL, &buffersize, &intStatus);
if (U_FAILURE(intStatus)) { return *this; }
if (dataIsOwned) {
ucol_close(ucollator);
}
ucollator = ucol;
dataIsOwned = TRUE;
isWriteThroughAlias = FALSE;
setRuleStringFromCollator();
return *this;
}
// aliasing, not write-through
Collator* RuleBasedCollator::clone() const
{
RuleBasedCollator* coll = new RuleBasedCollator(*this);
// There is a small chance that the internal ucol_safeClone() call fails.
if (coll != NULL && coll->ucollator == NULL) {
delete coll;
return NULL;
}
return coll;
}
CollationElementIterator* RuleBasedCollator::createCollationElementIterator
(const UnicodeString& source) const
{
UErrorCode status = U_ZERO_ERROR;
CollationElementIterator *result = new CollationElementIterator(source, this,
status);
if (U_FAILURE(status)) {
delete result;
return NULL;
}
return result;
}
/**
* Create a CollationElementIterator object that will iterate over the
* elements in a string, using the collation rules defined in this
* RuleBasedCollator
*/
CollationElementIterator* RuleBasedCollator::createCollationElementIterator
(const CharacterIterator& source) const
{
UErrorCode status = U_ZERO_ERROR;
CollationElementIterator *result = new CollationElementIterator(source, this,
status);
if (U_FAILURE(status)) {
delete result;
return NULL;
}
return result;
}
/**
* Return a string representation of this collator's rules. The string can
* later be passed to the constructor that takes a UnicodeString argument,
* which will construct a collator that's functionally identical to this one.
* You can also allow users to edit the string in order to change the collation
* data, or you can print it out for inspection, or whatever.
*/
const UnicodeString& RuleBasedCollator::getRules() const
{
return urulestring;
}
void RuleBasedCollator::getRules(UColRuleOption delta, UnicodeString &buffer)
{
int32_t rulesize = ucol_getRulesEx(ucollator, delta, NULL, -1);
if (rulesize > 0) {
UChar *rules = (UChar*) uprv_malloc( sizeof(UChar) * (rulesize) );
if(rules != NULL) {
ucol_getRulesEx(ucollator, delta, rules, rulesize);
buffer.setTo(rules, rulesize);
uprv_free(rules);
} else { // couldn't allocate
buffer.remove();
}
}
else {
buffer.remove();
}
}
UnicodeSet *
RuleBasedCollator::getTailoredSet(UErrorCode &status) const
{
if(U_FAILURE(status)) {
return NULL;
}
return (UnicodeSet *)ucol_getTailoredSet(this->ucollator, &status);
}
void RuleBasedCollator::getVersion(UVersionInfo versionInfo) const
{
if (versionInfo!=NULL){
ucol_getVersion(ucollator, versionInfo);
}
}
/**
* Compare two strings using this collator
*/
UCollationResult RuleBasedCollator::compare(
const UnicodeString& source,
const UnicodeString& target,
int32_t length,
UErrorCode &status) const
{
return compare(source.getBuffer(), uprv_min(length,source.length()), target.getBuffer(), uprv_min(length,target.length()), status);
}
UCollationResult RuleBasedCollator::compare(const UChar* source,
int32_t sourceLength,
const UChar* target,
int32_t targetLength,
UErrorCode &status) const
{
if(U_SUCCESS(status)) {
return ucol_strcoll(ucollator, source, sourceLength, target, targetLength);
} else {
return UCOL_EQUAL;
}
}
UCollationResult RuleBasedCollator::compare(
const UnicodeString& source,
const UnicodeString& target,
UErrorCode &status) const
{
if(U_SUCCESS(status)) {
return ucol_strcoll(ucollator, source.getBuffer(), source.length(),
target.getBuffer(), target.length());
} else {
return UCOL_EQUAL;
}
}
UCollationResult RuleBasedCollator::compare(UCharIterator &sIter,
UCharIterator &tIter,
UErrorCode &status) const {
if(U_SUCCESS(status)) {
return ucol_strcollIter(ucollator, &sIter, &tIter, &status);
} else {
return UCOL_EQUAL;
}
}
/**
* Retrieve a collation key for the specified string. The key can be compared
* with other collation keys using a bitwise comparison (e.g. memcmp) to find
* the ordering of their respective source strings. This is handy when doing a
* sort, where each sort key must be compared many times.
*
* The basic algorithm here is to find all of the collation elements for each
* character in the source string, convert them to an ASCII representation, and
* put them into the collation key. But it's trickier than that. Each
* collation element in a string has three components: primary ('A' vs 'B'),
* secondary ('u' vs '\u00FC'), and tertiary ('A' vs 'a'), and a primary difference
* at the end of a string takes precedence over a secondary or tertiary
* difference earlier in the string.
*
* To account for this, we put all of the primary orders at the beginning of
* the string, followed by the secondary and tertiary orders. Each set of
* orders is terminated by nulls so that a key for a string which is a initial
* substring of another key will compare less without any special case.
*
* Here's a hypothetical example, with the collation element represented as a
* three-digit number, one digit for primary, one for secondary, etc.
*
* String: A a B \u00C9
* Collation Elements: 101 100 201 511
* Collation Key: 1125<null>0001<null>1011<null>
*
* To make things even trickier, secondary differences (accent marks) are
* compared starting at the *end* of the string in languages with French
* secondary ordering. But when comparing the accent marks on a single base
* character, they are compared from the beginning. To handle this, we reverse
* all of the accents that belong to each base character, then we reverse the
* entire string of secondary orderings at the end.
*/
CollationKey& RuleBasedCollator::getCollationKey(
const UnicodeString& source,
CollationKey& sortkey,
UErrorCode& status) const
{
return getCollationKey(source.getBuffer(), source.length(), sortkey, status);
}
CollationKey& RuleBasedCollator::getCollationKey(const UChar* source,
int32_t sourceLen,
CollationKey& sortkey,
UErrorCode& status) const
{
if (U_FAILURE(status)) {
return sortkey.setToBogus();
}
if (sourceLen < -1 || (source == NULL && sourceLen != 0)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return sortkey.setToBogus();
}
if (sourceLen < 0) {
sourceLen = u_strlen(source);
}
if (sourceLen == 0) {
return sortkey.reset();
}
int32_t resultLen = ucol_getCollationKey(ucollator, source, sourceLen, sortkey, status);
if (U_SUCCESS(status)) {
sortkey.setLength(resultLen);
} else {
sortkey.setToBogus();
}
return sortkey;
}
/**
* Return the maximum length of any expansion sequences that end with the
* specified comparison order.
* @param order a collation order returned by previous or next.
* @return the maximum length of any expansion seuences ending with the
* specified order or 1 if collation order does not occur at the end of any
* expansion sequence.
* @see CollationElementIterator#getMaxExpansion
*/
int32_t RuleBasedCollator::getMaxExpansion(int32_t order) const
{
uint8_t result;
UCOL_GETMAXEXPANSION(ucollator, (uint32_t)order, result);
return result;
}
uint8_t* RuleBasedCollator::cloneRuleData(int32_t &length,
UErrorCode &status)
{
return ucol_cloneRuleData(ucollator, &length, &status);
}
int32_t RuleBasedCollator::cloneBinary(uint8_t *buffer, int32_t capacity, UErrorCode &status)
{
return ucol_cloneBinary(ucollator, buffer, capacity, &status);
}
void RuleBasedCollator::setAttribute(UColAttribute attr,
UColAttributeValue value,
UErrorCode &status)
{
if (U_FAILURE(status))
return;
checkOwned();
ucol_setAttribute(ucollator, attr, value, &status);
}
UColAttributeValue RuleBasedCollator::getAttribute(UColAttribute attr,
UErrorCode &status) const
{
if (U_FAILURE(status))
return UCOL_DEFAULT;
return ucol_getAttribute(ucollator, attr, &status);
}
uint32_t RuleBasedCollator::setVariableTop(const UChar *varTop, int32_t len, UErrorCode &status) {
checkOwned();
return ucol_setVariableTop(ucollator, varTop, len, &status);
}
uint32_t RuleBasedCollator::setVariableTop(const UnicodeString &varTop, UErrorCode &status) {
checkOwned();
return ucol_setVariableTop(ucollator, varTop.getBuffer(), varTop.length(), &status);
}
void RuleBasedCollator::setVariableTop(uint32_t varTop, UErrorCode &status) {
checkOwned();
ucol_restoreVariableTop(ucollator, varTop, &status);
}
uint32_t RuleBasedCollator::getVariableTop(UErrorCode &status) const {
return ucol_getVariableTop(ucollator, &status);
}
int32_t RuleBasedCollator::getSortKey(const UnicodeString& source,
uint8_t *result, int32_t resultLength)
const
{
return ucol_getSortKey(ucollator, source.getBuffer(), source.length(), result, resultLength);
}
int32_t RuleBasedCollator::getSortKey(const UChar *source,
int32_t sourceLength, uint8_t *result,
int32_t resultLength) const
{
return ucol_getSortKey(ucollator, source, sourceLength, result, resultLength);
}
int32_t RuleBasedCollator::getReorderCodes(int32_t *dest,
int32_t destCapacity,
UErrorCode& status) const
{
return ucol_getReorderCodes(ucollator, dest, destCapacity, &status);
}
void RuleBasedCollator::setReorderCodes(const int32_t *reorderCodes,
int32_t reorderCodesLength,
UErrorCode& status)
{
checkOwned();
ucol_setReorderCodes(ucollator, reorderCodes, reorderCodesLength, &status);
}
int32_t RuleBasedCollator::getEquivalentReorderCodes(int32_t reorderCode,
int32_t* dest,
int32_t destCapacity,
UErrorCode& status)
{
return ucol_getEquivalentReorderCodes(reorderCode, dest, destCapacity, &status);
}
/**
* Create a hash code for this collation. Just hash the main rule table -- that
* should be good enough for almost any use.
*/
int32_t RuleBasedCollator::hashCode() const
{
int32_t length;
const UChar *rules = ucol_getRules(ucollator, &length);
return ustr_hashUCharsN(rules, length);
}
/**
* return the locale of this collator
*/
Locale RuleBasedCollator::getLocale(ULocDataLocaleType type, UErrorCode &status) const {
const char *result = ucol_getLocaleByType(ucollator, type, &status);
if(result == NULL) {
Locale res("");
res.setToBogus();
return res;
} else {
return Locale(result);
}
}
void
RuleBasedCollator::setLocales(const Locale& requestedLocale, const Locale& validLocale, const Locale& actualLocale) {
checkOwned();
char* rloc = uprv_strdup(requestedLocale.getName());
if (rloc) {
char* vloc = uprv_strdup(validLocale.getName());
if (vloc) {
char* aloc = uprv_strdup(actualLocale.getName());
if (aloc) {
ucol_setReqValidLocales(ucollator, rloc, vloc, aloc);
return;
}
uprv_free(vloc);
}
uprv_free(rloc);
}
}
// RuleBaseCollatorNew private constructor ----------------------------------
RuleBasedCollator::RuleBasedCollator()
: dataIsOwned(FALSE), isWriteThroughAlias(FALSE), ucollator(NULL)
{
}
RuleBasedCollator::RuleBasedCollator(const Locale& desiredLocale,
UErrorCode& status)
: dataIsOwned(FALSE), isWriteThroughAlias(FALSE), ucollator(NULL)
{
if (U_FAILURE(status))
return;
/*
Try to load, in order:
1. The desired locale's collation.
2. A fallback of the desired locale.
3. The default locale's collation.
4. A fallback of the default locale.
5. The default collation rules, which contains en_US collation rules.
To reiterate, we try:
Specific:
language+country+variant
language+country
language
Default:
language+country+variant
language+country
language
Root: (aka DEFAULTRULES)
steps 1-5 are handled by resource bundle fallback mechanism.
however, in a very unprobable situation that no resource bundle
data exists, step 5 is repeated with hardcoded default rules.
*/
setUCollator(desiredLocale, status);
if (U_FAILURE(status))
{
status = U_ZERO_ERROR;
setUCollator(kRootLocaleName, status);
if (status == U_ZERO_ERROR) {
status = U_USING_DEFAULT_WARNING;
}
}
if (U_SUCCESS(status))
{
setRuleStringFromCollator();
}
}
void
RuleBasedCollator::setUCollator(const char *locale,
UErrorCode &status)
{
if (U_FAILURE(status)) {
return;
}
if (ucollator && dataIsOwned)
ucol_close(ucollator);
ucollator = ucol_open_internal(locale, &status);
dataIsOwned = TRUE;
isWriteThroughAlias = FALSE;
}
void
RuleBasedCollator::checkOwned() {
if (!(dataIsOwned || isWriteThroughAlias)) {
UErrorCode status = U_ZERO_ERROR;
ucollator = ucol_safeClone(ucollator, NULL, NULL, &status);
setRuleStringFromCollator();
dataIsOwned = TRUE;
isWriteThroughAlias = FALSE;
}
}
int32_t RuleBasedCollator::internalGetShortDefinitionString(const char *locale,
char *buffer,
int32_t capacity,
UErrorCode &status) const {
/* simply delegate */
return ucol_getShortDefinitionString(ucollator, locale, buffer, capacity, &status);
}
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedCollator)
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_COLLATION */