root/src/kits/locale/HashMapCatalog.cpp
/*
 * Copyright 2009, Adrien Destugues, pulkomandy@gmail.com. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include <HashMapCatalog.h>

#include <ByteOrder.h>

#include <stdlib.h>


namespace BPrivate {


/*
 * This is the standard implementation of a localization catalog, using a hash
 * map. This class is abstract, you need to inherit it and provide methodes for
 * reading and writing the catalog to a file. Classes doing that are
 * HashMapCatalog and PlainTextCatalog.
 * If you ever need to create a catalog not built around an hash map, inherit
 * BCatalogData instead. Note that in this case you will not be able to use our
 * development tools anymore.
 */


CatKey::CatKey(const char *str, const char *ctx, const char *cmt)
        :
        fString(str),
        fContext(ctx),
        fComment(cmt),
        fFlags(0)
{
        fHashVal = HashFun(fString.String(),0);
        fHashVal = HashFun(fContext.String(),fHashVal);
        fHashVal = HashFun(fComment.String(),fHashVal);
}


CatKey::CatKey(uint32 id)
        :
        fHashVal(id),
        fFlags(0)
{
}


CatKey::CatKey()
        :
        fHashVal(0),
        fFlags(0)
{
}


bool
CatKey::operator== (const CatKey& right) const
{
        // Two keys are equal if their hashval and key (string,context,comment)
        // are equal (testing only the hash would not filter out collisions):
        return fHashVal == right.fHashVal
                && fString == right.fString
                && fContext == right.fContext
                && fComment == right.fComment;
}


bool
CatKey::operator!= (const CatKey& right) const
{
        // Two keys are equal if their hashval and key (string,context,comment)
        // are equal (testing only the hash would not filter out collisions):
        return fHashVal != right.fHashVal
                || fString != right.fString
                || fContext != right.fContext
                || fComment != right.fComment;
}


status_t
CatKey::GetStringParts(BString* str, BString* ctx, BString* cmt) const
{
        if (str) *str = fString;
        if (ctx) *ctx = fContext;
        if (cmt) *cmt = fComment;

        return B_OK;
}


uint32
CatKey::HashFun(const char* s, int startValue) {
        unsigned long h = startValue;
        for ( ; *s; ++s)
                h = 5 * h + *s;

        // Add 1 to differenciate ("ab","cd","ef") from ("abcd","e","f")
        h = 5 * h + 1;

        return size_t(h);
}


// HashMapCatalog


void
HashMapCatalog::MakeEmpty()
{
        fCatMap.Clear();
}


int32
HashMapCatalog::CountItems() const
{
        return fCatMap.Size();
}


const char *
HashMapCatalog::GetString(const char *string, const char *context,
        const char *comment)
{
        CatKey key(string, context, comment);
        return GetString(key);
}


const char *
HashMapCatalog::GetString(uint32 id)
{
        CatKey key(id);
        return GetString(key);
}


const char *
HashMapCatalog::GetString(const CatKey& key)
{
        BString value = fCatMap.Get(key);
        if (value.Length() == 0)
                return NULL;
        else
                return value.String();
}


static status_t
parseQuotedChars(BString& stringToParse)
{
        char* in = stringToParse.LockBuffer(0);
        if (in == NULL)
                return B_ERROR;
        char* out = in;
        int newLength = 0;
        bool quoted = false;

        while (*in != 0) {
                if (quoted) {
                        if (*in == 'a')
                                *out = '\a';
                        else if (*in == 'b')
                                *out = '\b';
                        else if (*in == 'f')
                                *out = '\f';
                        else if (*in == 'n')
                                *out = '\n';
                        else if (*in == 'r')
                                *out = '\r';
                        else if (*in == 't')
                                *out = '\t';
                        else if (*in == 'v')
                                *out = '\v';
                        else if (*in == '"')
                                *out = '"';
                        else if (*in == 'x') {
                                if (in[1] == '\0' || in[2] == '\0')
                                        break;
                                // Parse the 2-digit hex integer that follows
                                char tmp[3];
                                tmp[0] = in[1];
                                tmp[1] = in[2];
                                tmp[2] = '\0';
                                unsigned int hexchar = strtoul(tmp, NULL, 16);
                                *out = hexchar;
                                // skip the number
                                in += 2;
                        } else {
                                // drop quote from unknown quoting-sequence:
                                *out = *in ;
                        }
                        quoted = false;
                        out++;
                        newLength++;
                } else {
                        quoted = (*in == '\\');
                        if (!quoted) {
                                *out = *in;
                                out++;
                                newLength++;
                        }
                }
                in++;
        }
        *out = '\0';
        stringToParse.UnlockBuffer(newLength);

        return B_OK;
}


status_t
HashMapCatalog::SetString(const char *string, const char *translated,
        const char *context, const char *comment)
{
        BString stringCopy(string);
        status_t result = parseQuotedChars(stringCopy);
        if (result != B_OK)
                return result;

        BString translatedCopy(translated);
        if ((result = parseQuotedChars(translatedCopy)) != B_OK)
                return result;

        BString commentCopy(comment);
        if ((result = parseQuotedChars(commentCopy)) != B_OK)
                return result;

        CatKey key(stringCopy.String(), context, commentCopy.String());
        return fCatMap.Put(key, translatedCopy.String());
                // overwrite existing element
}


status_t
HashMapCatalog::SetString(int32 id, const char *translated)
{
        BString translatedCopy(translated);
        status_t result = parseQuotedChars(translatedCopy);
        if (result != B_OK)
                return result;
        CatKey key(id);
        return fCatMap.Put(key, translatedCopy.String());
                // overwrite existing element
}


status_t
HashMapCatalog::SetString(const CatKey& key, const char *translated)
{
        BString translatedCopy(translated);
        status_t result = parseQuotedChars(translatedCopy);
        if (result != B_OK)
                return result;
        return fCatMap.Put(key, translatedCopy.String());
                // overwrite existing element
}


/*
 * computes a checksum (we call it fingerprint) on all the catalog-keys. We do
 * not include the values, since we want catalogs for different languages of the
 * same app to have the same fingerprint, since we use it to separate different
 * catalog-versions. We use a simple sum because there is no well known
 * checksum algorithm that gives the same result if the string are sorted in the
 * wrong order, and this does happen, as an hash map is an unsorted container.
 */
uint32
HashMapCatalog::ComputeFingerprint() const
{
        uint32 checksum = 0;

        int32 hash;
        CatMap::Iterator iter = fCatMap.GetIterator();
        CatMap::Entry entry;
        while (iter.HasNext()) {
                entry = iter.Next();
                hash = B_HOST_TO_LENDIAN_INT32(entry.key.fHashVal);
                checksum += hash;
        }
        return checksum;
}


void
HashMapCatalog::UpdateFingerprint()
{
        fFingerprint = ComputeFingerprint();
}


}       // namespace BPrivate