blob: f7a0fff9e8c55793879521a5cd08930d1565aa55 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 1996-1999, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
//=============================================================================
//
// File mergecol.cpp
//
// Contains MergeCollation. This classes job is to take one or more
// strings that represent the orderings in a collation, in the form
// "a , A < b , B ....". MergeCollation parses the string into a list of
// PatternEntry objects that are sorted by their position in the collation
// ordering. The input string is allowed to have elements out of order, e.g.
// "... b < c < d < e ..... c < ch". After being parsed by MergeCollation,
// the pattern entries will be in the proper order: "b", "c", "ch", "d", "e"
//
// Created by: Helena Shih
//
// Modification History:
//
// Date Name Description
// 3/5/97 mark Cleaned up fixEntry(). Added constants BYTEPOWER
// and BYTEMASK to replace BYTESIZE.
// 6/17/97 helena In getPattern, added the queue-up list for entries
// with the same extension chars.
// 6/23/97 helena Adding comments to make code more readable.
// 8/13/98 erm Synched up with 1.2 version of MergeCollation.java
// 04/23/99 stephen Removed EDecompositionMode, merged with
// Normalizer::EMode
//=============================================================================
#include "mergecol.h"
#include "tables.h"
#ifdef _DEBUG
#include "unistrm.h"
#endif
const int32_t MergeCollation::BITARRAYSIZE = 8192;
const uint8_t MergeCollation::BITARRAYMASK = 0x1;
const int32_t MergeCollation::BYTEPOWER = 3;
const int32_t MergeCollation::BYTEMASK = (1 << BYTEPOWER) - 1;
/**
* Creates from a pattern.
* If the input pattern is incorrect, error code will be set.
* @param pattern the merge collation pattern
* @param success error code input/output parameter.
*/
MergeCollation::MergeCollation(const UnicodeString& pattern,
Normalizer::EMode decompMode,
UErrorCode& status)
: lastEntry(NULL), saveEntry(NULL)
{
patterns = new VectorOfPointersToPatternEntry();
if (patterns == NULL)
{
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
statusArray = new uint8_t[BITARRAYSIZE];
if (statusArray == NULL)
{
delete patterns;
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
int32_t i;
for (i = 0; i < BITARRAYSIZE; i += 1)
{
statusArray[i] = 0;
}
setPattern(pattern, decompMode, status);
if (U_FAILURE(status))
{
delete [] statusArray;
statusArray = NULL;
}
}
/**
* Copy constructor
* @param other the source merge collation object to be constructed with
*/
MergeCollation::MergeCollation(const MergeCollation& other)
: lastEntry(NULL), saveEntry(NULL)
{
// This copy ctor does a deep copy - it duplicates the PatternEntry
// objects as well as the vector object
patterns = new VectorOfPointersToPatternEntry(*other.patterns);
int32_t i;
statusArray = new uint8_t[BITARRAYSIZE];
for (i = 0; i < BITARRAYSIZE; i += 1)
{
statusArray[i] = other.statusArray[i];
}
}
// Assignment operator. Does a deep copy.
const MergeCollation&
MergeCollation::operator=(const MergeCollation& other)
{
if (this != &other)
{
*patterns = *other.patterns;
if(lastEntry != 0) {
if (lastEntry->strength == PatternEntry::RESET) {
delete lastEntry;
}
}
lastEntry = 0;
saveEntry = 0;
int32_t i;
for (i = 0; i < BITARRAYSIZE; i += 1)
{
statusArray[i] = other.statusArray[i];
}
}
return *this;
}
/**
* Destructor
*/
MergeCollation::~MergeCollation()
{
if(lastEntry != 0) {
if (lastEntry->strength == PatternEntry::RESET) {
delete lastEntry;
}
}
delete patterns;
delete [] statusArray;
}
/**
* recovers current pattern as a string.
* Basically, this runs through the PatternEntry array and outputs
* @param result the string into which the pattern is recovered
* the proper string for each element.
* @param withWhiteSpace puts spacing around the entries, and \n
* before & and <
*/
UnicodeString&
MergeCollation::getPattern(UnicodeString& result, UBool withWhiteSpace) const
{
int32_t i;
PatternEntry *tmp = NULL;
VectorOfPointer *extList = NULL;
result.remove();
for (i = 0; i < patterns->size(); i += 1)
{
PatternEntry* entry = patterns->at(i);
if (entry != NULL)
{
// if the entry is an expanding ligature, queue up the entries until
// the last same ligature has been processed.
if (entry->extension.length() != 0)
{
if (extList == NULL)
{
extList = new VectorOfPointer();
}
extList->atInsert(extList->size(), (const void*&)entry);
}
else
{
// Process the queue-up list in reverse order to get the correct
// pattern.
if (extList != NULL)
{
const PatternEntry *last = findLastWithNoExtension(i - 1);
for (int32_t j = extList->size() - 1; j >= 0 ; j -= 1)
{
tmp = (PatternEntry*)(extList->at(j));
tmp->addToBuffer(result, FALSE, withWhiteSpace, last);
}
delete extList;
extList = NULL;
}
entry->addToBuffer(result, FALSE, withWhiteSpace, NULL);
}
}
}
// Complete the queue-up list if it isn't empty
if (extList != NULL)
{
const PatternEntry *last = findLastWithNoExtension(i - 1);
for (int32_t j = extList->size() - 1; j >= 0 ; j -= 1)
{
tmp = (PatternEntry*)(extList->at(j));
tmp->addToBuffer(result, FALSE, withWhiteSpace, last);
}
delete extList;
}
return result;
}
/**
* emits the pattern for collation builder.
* @param result the string into which the pattern is recovered
* @param withWhiteSpace puts spacing around the entries, and \n
* before & and <
* @return emits the string in the format understable to the collation
* builder.
*/
UnicodeString&
MergeCollation::emitPattern(UnicodeString& result, UBool withWhiteSpace) const
{
int32_t i;
result.remove();
for (i = 0; i < patterns->size(); i += 1)
{
PatternEntry *entry = (PatternEntry *)patterns->at(i);
if (entry != NULL)
{
entry->addToBuffer(result, TRUE, withWhiteSpace, NULL);
}
}
return result;
}
/**
* sets the pattern.
*/
void MergeCollation::setPattern(const UnicodeString& pattern,
Normalizer::EMode decompMode,
UErrorCode& success)
{
if (U_FAILURE(success))
{
return;
}
patterns->clear();
addPattern(pattern, decompMode, success);
if (U_FAILURE(success))
{
delete patterns;
patterns = NULL;
}
}
/**
* adds a pattern string to the current list of patterns
* @param pattern the new pattern to be added
*/
void MergeCollation::addPattern(const UnicodeString& pattern,
Normalizer::EMode decompMode,
UErrorCode& success)
{
if (U_FAILURE(success) || (pattern.length() == 0))
{
return;
}
PatternEntry::Parser *parser = new PatternEntry::Parser(pattern, decompMode);
PatternEntry *entry = parser->next(success);
while (entry != NULL)
{
if (U_FAILURE(success))
{
delete entry;
break;
}
fixEntry(entry, success);
if (U_FAILURE(success))
{
delete entry;
break;
}
entry = parser->next(success);
}
// WEIV tentatively - is there a leak at the end???
delete parser;
}
/**
* gets count of separate entries
* @return the size of pattern entries
*/
int32_t
MergeCollation::getCount() const {
return patterns->size();
}
/**
* gets count of separate entries
* @param index the offset of the desired pattern entry
* @return the requested pattern entry
*/
const PatternEntry* MergeCollation::getItemAt(UTextOffset index) const {
return patterns->at(index);
}
// Find the last no-extension entry.
const PatternEntry* MergeCollation::findLastWithNoExtension(int32_t i) const {
for (--i;i >= 0; --i) {
PatternEntry* entry = patterns->at(i);
if ((entry != 0) && (entry->extension.length() == 0)) {
return entry;
}
}
return 0;
}
// Add a new PatternEntry to this MergeCollation's ordered list
// of entries.
//
// If the strength is RESET, then just change the lastEntry to
// be the current. (If the current is not in patterns, signal an error).
//
// If not, then remove the current entry, and add it after lastEntry
// (which is usually at the end).
//
void MergeCollation::fixEntry(PatternEntry* newEntry,
UErrorCode& success) {
UnicodeString excess;
UBool changeLastEntry = TRUE;
if (newEntry->strength != PatternEntry::RESET) {
int32_t oldIndex = -1;
// Use statusArray to mark if a unicode character has been
// added in the table or not. The same later entry will
// replace the previous one. This will improve the single
// char entries dramatically which is the majority of the
// entries.
if (newEntry->chars.length() == 1) {
UChar c = newEntry->chars[0];
int32_t statusIndex = c >> BYTEPOWER;
uint8_t bitClump = statusArray[statusIndex];
uint8_t setBit = (uint8_t)(BITARRAYMASK << (c & BYTEMASK));
if (bitClump != 0 && (bitClump & setBit) != 0) {
int32_t i = 0;
// Find the previous entry with the same key
for (i = patterns->size() - 1; i >= 0; i -= 1) {
PatternEntry *entry = patterns->at(i);
if ((entry != 0) &&
(entry->chars == newEntry->chars)) {
oldIndex = i;
break;
}
}
} else {
// We're going to add an element that starts with this
// character, so go ahead and set its bit.
statusArray[statusIndex] = (uint8_t)(bitClump | setBit);
}
} else {
oldIndex = patterns->lastIndexOf(newEntry);
}
int32_t lastIndex = -1;
if (oldIndex != -1) {
PatternEntry *p = patterns->orphanAt(oldIndex);
if (lastEntry == p) {
lastEntry = 0; // Prevent double deletion
lastIndex = patterns->size();
}
delete p;
}
if (lastIndex < 0) {
// Find the insertion point for the new entry.
lastIndex = findLastEntry(lastEntry, excess, success);
if (U_FAILURE(success)) {
return;
}
}
// Do not change the last entry if the new entry is a expanding character
if (excess.length() != 0) {
// newEntry.extension = excess + newEntry.extensions;
newEntry->extension.insert(0, excess);
if (lastIndex != patterns->size()) {
if (lastEntry != 0) {
if (lastEntry->strength == PatternEntry::RESET) {
delete lastEntry;
}
}
lastEntry = saveEntry;
changeLastEntry = FALSE;
}
}
// Add the entry at the end or insert it in the middle
if (lastIndex == patterns->size()) {
patterns->atPut(lastIndex, newEntry);
saveEntry = newEntry;
} else {
patterns->atInsert(lastIndex, newEntry); // add at end
}
}
if (changeLastEntry) {
if (lastEntry != 0) {
if (lastEntry->strength == PatternEntry::RESET) {
delete lastEntry;
}
}
lastEntry = newEntry;
}
}
int32_t
MergeCollation::findLastEntry(const PatternEntry* lastPatEntry,
UnicodeString& excess,
UErrorCode& success) const
{
if (U_FAILURE(success))
{
return -1;
}
if (lastPatEntry == NULL)
{
return 0;
}
else if (lastPatEntry->strength != PatternEntry::RESET)
{
int32_t oldIndex = -1;
// If the last entry is a single char entry and has been installed,
// that means the last index is the real last index.
if (lastPatEntry->chars.length() == 1)
{
int32_t index = lastPatEntry->chars[0] >> BYTEPOWER;
if ((statusArray[index] &
(uint8_t)(BITARRAYMASK << (lastPatEntry->chars[0] & BYTEMASK))) != 0)
{
oldIndex = patterns->lastIndexOf(lastPatEntry);
}
}
else
{
oldIndex = patterns->lastIndexOf(lastPatEntry);
}
// must exist!
if (oldIndex == -1)
{
success = U_INVALID_FORMAT_ERROR;
return oldIndex;
}
return oldIndex + 1;
}
else
{
// We're doing a reset, i.e. inserting a new ordering at the position
// just after the entry corresponding to lastPatEntry's first character
int32_t i;
// Search backwards for string that contains this one;
// most likely entry is last one
for (i = patterns->size() - 1; i >= 0; i -= 1)
{
PatternEntry* entry = patterns->at(i);
UnicodeString buffer;
if (entry != 0)
{
//
// Look for elements with the same beginning key. The extra
// characters will be the expanding portion. This handles cases like
// "& Question-mark < '?'". We find the existing PatternEntry that matches
// the longest possible substring of "Question-mark", which will probably
// be 'Q'. We save the characters that didn't match ("uestion-mark" in
// this case), and then return the next index.
//
if (entry->chars.compareBetween(0, entry->chars.length(),
lastPatEntry->chars,0,entry->chars.length()) == 0)
{
lastPatEntry->chars.extractBetween(entry->chars.length(),
lastPatEntry->chars.length(),
buffer);
excess += buffer;
break;
}
}
}
if (i == -1)
{
success = U_INVALID_FORMAT_ERROR;
return i;
}
return i + 1;
}
}