blob: 8ca6163277cd2ce2433893e5d2d717f89d25bae5 [file] [log] [blame]
/*
**********************************************************************
* Copyright (C) 1997-1999, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
*
* File resbund.cpp
*
* Modification History:
*
* Date Name Description
* 02/05/97 aliu Fixed bug in chopLocale. Added scanForLocaleInFile
* based on code taken from scanForLocale. Added
* constructor which attempts to read resource bundle
* from a specific file, without searching other files.
* 02/11/97 aliu Added UErrorCode return values to constructors. Fixed
* infinite loops in scanForFile and scanForLocale.
* Modified getRawResourceData to not delete storage in
* localeData and resourceData which it doesn't own.
* Added Mac compatibility #ifdefs for tellp() and
* ios::nocreate.
* 03/04/97 aliu Modified to use ExpandingDataSink objects instead of
* the highly inefficient ostrstream objects.
* 03/13/97 aliu Rewrote to load in entire resource bundle and store
* it as a Hashtable of ResourceBundleData objects.
* Added state table to govern parsing of files.
* Modified to load locale index out of new file distinct
* from default.txt.
* 03/25/97 aliu Modified to support 2-d arrays, needed for timezone data.
* Added support for custom file suffixes. Again, needed to
* support timezone data. Improved error handling to detect
* duplicate tags and subtags.
* 04/07/97 aliu Fixed bug in getHashtableForLocale(). Fixed handling of
* failing UErrorCode values on entry to API methods.
* Fixed bugs in getArrayItem() for negative indices.
* 04/29/97 aliu Update to use new Hashtable deletion protocol.
* 05/06/97 aliu Flattened kTransitionTable for HP compiler. Fixed usage of
* CharString.
* 06/11/99 stephen Removed parsing of .txt files.
* Reworked to use new binary format.
* Cleaned up.
* 06/14/99 stephen Removed methods taking a filename suffix.
* 06/22/99 stephen Added missing T_FileStream_close in parse()
* 11/09/99 weiv Added getLocale(), rewritten constructForLocale()
*******************************************************************************
*/
#include "rbcache.h"
#include "unicode/resbund.h"
#include "mutex.h"
#include "unistrm.h"
#include "filestrm.h"
#include "cstring.h"
#include "uhash.h"
#include "rbdata.h"
#include "rbread.h"
#include <iostream.h>
#include <string.h>
#include <wchar.h>
/*-----------------------------------------------------------------------------
* Implementation Notes
*
* Resource bundles are read in once, and thereafter cached.
* ResourceBundle statically keeps track of which files have been
* read, so we are guaranteed that each file is read at most once.
* Resource bundles can be loaded from different data directories and
* will be treated as distinct, even if they are for the same locale.
*
* Resource bundles are lightweight objects, which have pointers to
* one or more shared Hashtable objects containing all the data.
* Copying would be cheap, but there is no copy constructor, since
* there wasn't one in the original API.
*
* The ResourceBundle parsing mechanism is implemented as a transition
* network, for easy maintenance and modification. The network is
* implemented as a matrix (instead of in code) to make this even
* easier. The matrix contains Transition objects. Each Transition
* object describes a destination node and an action to take before
* moving to the destination node. The source node is encoded by the
* index of the object in the array that contains it. The pieces
* needed to understand the transition network are the enums for node
* IDs and actions, the parse() method, which walks through the
* network and implements the actions, and the network itself. The
* network guarantees certain conditions, for example, that a new
* resource will not be closed until one has been opened first; or
* that data will not be stored into a TaggedList until a TaggedList
* has been created. Nonetheless, the code in parse() does some
* consistency checks as it runs the network, and fails with an
* U_INTERNAL_PROGRAM_ERROR if one of these checks fails. If the input
* data has a bad format, an U_INVALID_FORMAT_ERROR is returned. If you
* see an U_INTERNAL_PROGRAM_ERROR the transition matrix has a bug in
* it.
*
* Old functionality of multiple locales in a single file is still
* supported. For this reason, LOCALE names override FILE names. If
* data for en_US is located in the en.txt file, once it is loaded,
* the code will not care where it came from (other than remembering
* which directory it came from). However, if there is an en_US
* resource in en_US.txt, that will take precedence. There is no
* limit to the number or type of resources that can be stored in a
* file, however, files are only searched in a specific way. If
* en_US_CA is requested, then first en_US_CA.txt is searched, then
* en_US.txt, then en.txt, then default.txt. So it only makes sense
* to put certain locales in certain files. In this example, it would
* be logical to put en_US_CA, en_US, and en into the en.txt file,
* since they would be found there if asked for. The extreme example
* is to place all locale resources into default.txt, which should
* also work.
*
* Inheritance is implemented. For example, xx_YY_zz inherits as
* follows: xx_YY_zz, xx_YY, xx, default. Inheritance is implemented
* as an array of hashtables. There will be from 1 to 4 hashtables in
* the array.
*
* Fallback files are implemented. The fallback pattern is Language
* Country Variant (LCV) -> LC -> L. Fallback is first done for the
* requested locale. Then it is done for the default locale, as
* returned by Locale::getDefault(). Then the special file
* default.txt is searched for the default locale. The overall FILE
* fallback path is LCV -> LC -> L -> dLCV -> dLC -> dL -> default.
*
* Note that although file name searching includes the default locale,
* once a ResourceBundle object is constructed, the inheritance path
* no longer includes the default locale. The path is LCV -> LC -> L
* -> default.
*
* File parsing is lazy. Nothing is parsed unless it is called for by
* someone. So when a ResourceBundle for xx_YY_zz is constructed,
* only that locale is parsed (along with anything else in the same
* file). Later, if the FooBar tag is asked for, and if it isn't
* found in xx_YY_zz, then xx_YY.txt will be parsed and checked, and
* so forth, until the chain is exhausted or the tag is found.
*
* Thread-safety is implemented around caches, both the cache that
* stores all the resouce data, and the cache that stores flags
* indicating whether or not a file has been visited. These caches
* delete their storage at static cleanup time, when the process
* quits.
*
* ResourceBundle supports TableCollation as a special case. This
* involves having special ResourceBundle objects which DO own their
* data, since we don't want large collation rule strings in the
* ResourceBundle cache (these are already cached in the
* TableCollation cache). TableCollation files (.ctx files) have the
* same format as normal resource data files, with a different
* interpretation, from the standpoint of ResourceBundle. .ctx files
* are loaded into otherwise ordinary ResourceBundle objects. They
* don't inherit (that's implemented by TableCollation) and they own
* their data (as mentioned above). However, they still support
* possible multiple locales in a single .ctx file. (This is in
* practice a bad idea, since you only want the one locale you're
* looking for, and only one tag will be present
* ("CollationElements"), so you don't need an inheritance chain of
* multiple locales.) Up to 4 locale resources will be loaded from a
* .ctx file; everything after the first 4 is ignored (parsed and
* deleted). (Normal .txt files have no limit.) Instead of being
* loaded into the cache, and then looked up as needed, the locale
* resources are read straight into the ResourceBundle object.
*
* The Index, which used to reside in default.txt, has been moved to a
* new file, index.txt. This file contains a slightly modified format
* with the addition of the "InstalledLocales" tag; it looks like:
*
* Index {
* InstalledLocales {
* ar
* ..
* zh_TW
* }
* }
*/
//-----------------------------------------------------------------------------
const char* ResourceBundle::kDefaultSuffix = ".res";
const int32_t ResourceBundle::kDefaultSuffixLen = 4;
const char* ResourceBundle::kDefaultFilename = "default";
const char* ResourceBundle::kDefaultLocaleName = "default";
const char* ResourceBundle::kIndexLocaleName = "index";
const char* ResourceBundle::kIndexFilename = "index";
const char* ResourceBundle::kIndexTag = "InstalledLocales";
// The default minor version and the version separator must be exactly one
// character long.
const char* ResourceBundle::kDefaultMinorVersion = "0";
const char* ResourceBundle::kVersionSeparator = ".";
const char* ResourceBundle::kVersionTag = "Version";
ResourceBundleCache* ResourceBundle::fgUserCache = new ResourceBundleCache();
VisitedFileCache* ResourceBundle::fgUserVisitedFiles = new VisitedFileCache();
// allocated on the heap so we don't have to expose the definitions of
// these classes to the world
//-----------------------------------------------------------------------------
ResourceBundle::LocaleFallbackIterator::LocaleFallbackIterator(const UnicodeString& startingLocale,
const UnicodeString& root,
bool_t useDefaultLocale)
: fLocale(startingLocale),
fRoot(root),
fUseDefaultLocale(useDefaultLocale),
fTriedDefaultLocale(FALSE),
fTriedRoot(FALSE)
{
if (fUseDefaultLocale) Locale::getDefault().getName(fDefaultLocale);
}
bool_t
ResourceBundle::LocaleFallbackIterator::nextLocale(UErrorCode& status)
{
if(fUseDefaultLocale)
fTriedDefaultLocale = fTriedDefaultLocale || (fLocale == fDefaultLocale);
chopLocale();
if(status != U_USING_DEFAULT_ERROR)
status = U_USING_FALLBACK_ERROR;
if(fLocale.length() == 0) {
if(fUseDefaultLocale && !fTriedDefaultLocale) {
fLocale = fDefaultLocale;
fTriedDefaultLocale = TRUE;
status = U_USING_DEFAULT_ERROR;
}
else if( ! fTriedRoot) {
fLocale = fRoot;
fTriedRoot = TRUE;
status = U_USING_DEFAULT_ERROR;
}
else {
status = U_MISSING_RESOURCE_ERROR;
return FALSE;
}
}
// cerr << "* " << fLocale << " " << u_errorName(status) << endl;
return TRUE;
}
void
ResourceBundle::LocaleFallbackIterator::chopLocale()
{
int32_t size = fLocale.length();
int32_t i;
for(i = size - 1; i > 0; i--)
if(fLocale[i] == 0x005F/*'_'*/)
break;
if(i < 0)
i = 0;
fLocale.remove(i, size - i);
}
//-----------------------------------------------------------------------------
ResourceBundle::ResourceBundle( const UnicodeString& path,
const Locale& locale,
UErrorCode& error)
: fgCache(fgUserCache),
fgVisitedFiles(fgUserVisitedFiles)
{
constructForLocale(PathInfo(path, kDefaultSuffix), locale, error);
}
ResourceBundle::ResourceBundle( const UnicodeString& path,
UErrorCode& error)
: fgCache(fgUserCache),
fgVisitedFiles(fgUserVisitedFiles)
{
constructForLocale(PathInfo(path, kDefaultSuffix),
Locale::getDefault(), error);
}
/**
* This constructor is used by TableCollation to load a resource
* bundle from a specific file, without trying other files. This is
* used by the TableCollation caching mechanism. This is not a public
* API constructor.
*/
ResourceBundle::ResourceBundle( const UnicodeString& path,
const UnicodeString& localeName,
UErrorCode& status)
: fPath(path, UnicodeString(kDefaultSuffix,"")),
fRealLocale(localeName),
fIsDataOwned(TRUE),
fVersionID(0),
fgCache(fgUserCache),
fgVisitedFiles(fgUserVisitedFiles)
{
status = U_ZERO_ERROR;
int32_t i;
for(i = 0; i < kDataCount; ++i) {
fData[i] = 0;
fLoaded[i] = FALSE;
fDataStatus[i] = U_INTERNAL_PROGRAM_ERROR;
}
fLocaleIterator = 0;
// If the file doesn't exist, return an error
if(fPath.fileExists(localeName)) {
parse(fPath, localeName, saveCollationHashtable,
(void*)this, fgCache, status);
}
else {
status = U_MISSING_RESOURCE_ERROR;
}
// Prevent further attempts to load hashtables
for(i = 0; i < kDataCount; ++i)
fLoaded[i] = TRUE;
}
void
ResourceBundle::saveCollationHashtable(const UnicodeString& localeName,
UHashtable* hashtable,
void* context,
ResourceBundleCache* fgCache)
{
ResourceBundle* bundle = (ResourceBundle*)context;
for(int32_t i = 0; i < kDataCount; ++i) {
if( ! bundle->fLoaded[i]) {
bundle->fData[i] = hashtable;
bundle->fLoaded[i] = TRUE;
bundle->fDataStatus[i] = U_ZERO_ERROR; /* ??? */
return;
}
}
// Out of room; discard extra data. We only expect to see one anyway.
uhash_close(hashtable);
}
ResourceBundle::ResourceBundle(const wchar_t* path,
const Locale& locale,
UErrorCode& err)
: fgCache(fgUserCache),
fgVisitedFiles(fgUserVisitedFiles)
{
int32_t wideNameLen = uprv_mbstowcs(NULL, kDefaultSuffix, kDefaultSuffixLen);
wchar_t* wideName = new wchar_t[wideNameLen + 1];
uprv_mbstowcs(wideName, kDefaultSuffix, kDefaultSuffixLen);
wideName[wideNameLen] = 0;
constructForLocale(PathInfo(path, wideName), locale, err);
delete [] wideName;
}
ResourceBundle::~ResourceBundle()
{
delete fLocaleIterator;
delete [] fVersionID;
if(fIsDataOwned)
for(int32_t i = 0; i < kDataCount; ++i) {
if(fData[i])
uhash_close((UHashtable*)fData[i]);
}
}
void
ResourceBundle::constructForLocale(const PathInfo& path,
const Locale& locale,
UErrorCode& error)
{
int32_t i;
fPath = path;
fIsDataOwned = FALSE;
fVersionID = 0;
// fRealLocale can be inited in three ways, see 1), 2), 3)
UnicodeString returnedLocale;
locale.getName(returnedLocale);
if (returnedLocale.length()!=0) {
// 1) Desired Locale has a name
fRealLocale = Locale(returnedLocale);
} else {
// 2) Desired Locale name is empty, so we use default locale for the system
fRealLocale = Locale(kDefaultLocaleName);
}
error = U_ZERO_ERROR;
for(i = 1; i < kDataCount; ++i) {
fData[i] = 0;
fDataStatus[i] = U_INTERNAL_PROGRAM_ERROR;
fLoaded[i] = FALSE;
}
error = U_ZERO_ERROR;
fData[0] = getHashtableForLocale(fRealLocale.getName(), returnedLocale, error);
fLoaded[0] = TRUE;
fDataStatus[0] = U_ZERO_ERROR;
if(U_SUCCESS(error))
// 3) We're unable to get the desired Locale, so we're using what is provided (fallback occured)
fRealLocale = Locale(returnedLocale);
fLocaleIterator = new LocaleFallbackIterator(fRealLocale.getName(),
kDefaultLocaleName, FALSE);
}
/**
* Return the hash table with data for the given locale. This method employs
* fallback both in files and in locale names. It returns the locale name
* which is actually used to return the data, if any.
*
* Parse all files found at the given path for the given path, in an effort
* to find data for the given locale. Use fallbacks and defaults as needed.
* Store read in file data in the cache for future use. Return the hashtable
* for the given locale, if found, or 0 if not.
*/
const UHashtable*
ResourceBundle::getHashtableForLocale(const UnicodeString& desiredLocale,
UnicodeString& returnedLocale,
UErrorCode& error)
{
if(U_FAILURE(error)) return 0;
error = U_ZERO_ERROR;
const UHashtable* h = getFromCache(fPath, desiredLocale, fgCache);
if(h != 0) {
returnedLocale = desiredLocale;
return h;
}
LocaleFallbackIterator iterator(desiredLocale, kDefaultFilename, TRUE);
bool_t didTryCacheWithFallback = FALSE;
// A note on fileError. We are tracking two different error states
// here. One is that returned while iterating over different files.
// For instance, when going from de_CH.txt to de.txt we will get a
// U_USING_FALLBACK_ERROR, but we don't care -- because if de.txt
// contains the de_CH locale, it isn't a fallback, from our
// perspective. Therefore we keep file associated errors in
// fileError, apart from the error parameter.
UErrorCode fileError = U_ZERO_ERROR;
for(;;) {
// Build a filename for the locale.
if(parseIfUnparsed(fPath, iterator.getLocale(),
fgCache, fgVisitedFiles, error)) {
if(U_FAILURE(error))
return 0;
error = U_ZERO_ERROR;
h = getFromCacheWithFallback(fPath, desiredLocale,
returnedLocale, fgCache, error);
didTryCacheWithFallback = TRUE;
if(h != 0 && U_SUCCESS(error))
return h;
}
if(!iterator.nextLocale(fileError)) {
error = U_MISSING_RESOURCE_ERROR;
break;
}
}
// We want to try loading from the cache will fallback at least
// once. These lines of code handle the case in which all of the
// fallback FILES have been loaded, so fgVisitedFiles keeps us from
// parsing them again. In this case we still want to make an
// attempt to load our locale from the cache.
if(didTryCacheWithFallback)
return 0;
error = U_ZERO_ERROR;
return getFromCacheWithFallback(fPath, desiredLocale,
returnedLocale, fgCache, error);
}
/**
* Return the hash table with data for the given locale. This method employs
* fallback in file names only. If data is returned, it will be exactly for
* the given locale.
*/
const UHashtable*
ResourceBundle::getHashtableForLocale(const UnicodeString& desiredLocale,
UErrorCode& error)
{
if(U_FAILURE(error))
return 0;
error = U_ZERO_ERROR;
// First try the cache
const UHashtable* h = getFromCache(fPath, desiredLocale, fgCache);
if(h != 0)
return h;
// Now try files
LocaleFallbackIterator iterator(desiredLocale, kDefaultFilename, FALSE);
for(;;) {
UErrorCode parseError = U_ZERO_ERROR;
if(parseIfUnparsed(fPath, iterator.getLocale(),
fgCache, fgVisitedFiles, parseError)) {
if(U_FAILURE(parseError)) {
error = parseError;
return 0;
}
const UHashtable* h = getFromCache(fPath, desiredLocale, fgCache);
if(h != 0)
return h;
}
if(!iterator.nextLocale(error))
return 0;
}
}
/**
* Try to retrieve a locale data hash from the cache, using fallbacks
* if necessary. Ultimately we will try to load the data under
* kDefaultLocaleName.
*/
const UHashtable*
ResourceBundle::getFromCacheWithFallback(const PathInfo& path,
const UnicodeString& desiredLocale,
UnicodeString& returnedLocale,
ResourceBundleCache* fgCache,
UErrorCode& error)
{
if(U_FAILURE(error))
return 0;
error = U_ZERO_ERROR;
LocaleFallbackIterator iterator(desiredLocale, kDefaultLocaleName, TRUE);
for(;;) {
const UHashtable* h = getFromCache(path, iterator.getLocale(), fgCache);
if(h != 0) {
returnedLocale = iterator.getLocale();
return h;
}
if(!iterator.nextLocale(error))
return 0;
}
}
/**
* Parse the given file, if it hasn't been attempted already, and if
* it actually exists. Return true if a parse is attempted. Upon
* return, if the return value is true, the error code may be set as a
* result of a parse failure to a failing value. If the parse was
* successful, additional entries may have been created in the cache.
*/
bool_t
ResourceBundle::parseIfUnparsed(const PathInfo& path,
const UnicodeString& locale,
ResourceBundleCache* fgCache,
VisitedFileCache* fgVisitedFiles,
UErrorCode& error)
{
UnicodeString key(path.makeCacheKey(locale));
if(!fgVisitedFiles->wasVisited(key) && path.fileExists(locale)) {
parse(path, locale, addToCache, (void*)&path, fgCache, error);
{
Mutex lock;
fgVisitedFiles->markAsVisited(key);
}
return TRUE;
}
return FALSE;
}
/**
* Given a tag, try to retrieve the data for that tag. This method is
* semantically const, but may actually modify this object. All
* public API methods such as getString() rely on getDataForTag()
* ultimately. This method implements inheritance of data between
* locales.
*/
const ResourceBundleData*
ResourceBundle::getDataForTag(const char *tag,
UErrorCode& err) const
{
err = U_ZERO_ERROR; /* just to make sure there's no fallback/etc left over */
// Iterate over the kDataCount hashtables which may be associated with this
// bundle. At most we have kDataCount, but we may have as few as one.
for(int32_t i = 0; i < kDataCount; ++i) {
// First try to load up this hashtable, if it hasn't been loaded yet.
if(!fLoaded[i] && fData[i] == 0) {
ResourceBundle* nonconst = (ResourceBundle*)this;
nonconst->fLoaded[i] = TRUE;
if(fLocaleIterator->nextLocale(err)) {
UErrorCode getHashtableStatus = U_ZERO_ERROR;
nonconst->fDataStatus[i] = err;
nonconst->fData[i] =
nonconst->getHashtableForLocale(fLocaleIterator->getLocale(), getHashtableStatus);
}
}
if(fData[i] != 0) {
const ResourceBundleData* s =
(const ResourceBundleData*)uhash_get(fData[i],
UnicodeString(tag, "").hashCode() & 0x7FFFFFFF);
if(s != 0) {
err = fDataStatus[i]; /* restore the error from the original lookup. */
return s;
}
}
}
#ifdef _DEBUG
// cerr << "Failed to find tag " << tag << " in " << fPath << fRealLocaleID << fFilenameSuffix << endl;
// cerr << *this;
#endif
err = U_MISSING_RESOURCE_ERROR;
return 0;
}
void
ResourceBundle::getString( const char *resourceTag,
UnicodeString& theString,
UErrorCode& err) const
{
if(U_FAILURE(err))
return;
const UnicodeString* temp = getString(resourceTag, err);
if(U_SUCCESS(err))
theString = *temp;
}
const UnicodeString*
ResourceBundle::getString( const char *resourceTag,
UErrorCode& err) const
{
if(U_FAILURE(err))
return NULL;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == StringList::getStaticClassID()
&& ((StringList*)data)->fCount == 1) {
return &(((StringList*)data)->fStrings[0]);
}
else err = U_MISSING_RESOURCE_ERROR;
return NULL;
}
const UnicodeString*
ResourceBundle::getStringArray( const char *resourceTag,
int32_t& count,
UErrorCode& err) const
{
if(U_FAILURE(err))
return 0;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == StringList::getStaticClassID()) {
count = ((StringList*)data)->fCount;
return ((StringList*)data)->fStrings;
}
err = U_MISSING_RESOURCE_ERROR;
return 0;
}
void
ResourceBundle::getArrayItem( const char *resourceTag,
int32_t index,
UnicodeString& theArrayItem,
UErrorCode& err) const
{
if(U_FAILURE(err))
return;
const UnicodeString* temp = getArrayItem(resourceTag, index, err);
if(U_SUCCESS(err))
theArrayItem = *temp;
}
const UnicodeString*
ResourceBundle::getArrayItem( const char *resourceTag,
int32_t index,
UErrorCode& err) const
{
if(U_FAILURE(err))
return NULL;
// Casting to unsigned turns a signed value into a large unsigned
// value. This allows us to do one comparison to check that 0 <=
// index < count, instead of two separate comparisons for each index
// check.
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == StringList::getStaticClassID()
&& (uint32_t)index < (uint32_t)((StringList*)data)->fCount) {
return &(((StringList*)data)->fStrings[index]);
}
else
err = U_MISSING_RESOURCE_ERROR;
return NULL;
}
const UnicodeString**
ResourceBundle::get2dArray(const char *resourceTag,
int32_t& rowCount,
int32_t& columnCount,
UErrorCode& err) const
{
if(U_FAILURE(err))
return 0;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == String2dList::getStaticClassID()) {
String2dList *list = (String2dList*)data;
rowCount = list->fRowCount;
columnCount = list->fColCount;
// Why is this cast required? It shouldn't be. [LIU]
return (const UnicodeString**)list->fStrings;
}
err = U_MISSING_RESOURCE_ERROR;
return 0;
}
void
ResourceBundle::get2dArrayItem(const char *resourceTag,
int32_t rowIndex,
int32_t columnIndex,
UnicodeString& theArrayItem,
UErrorCode& err) const
{
if(U_FAILURE(err))
return;
const UnicodeString* temp = get2dArrayItem(resourceTag, rowIndex,
columnIndex, err);
if(U_SUCCESS(err))
theArrayItem = *temp;
}
const UnicodeString*
ResourceBundle::get2dArrayItem(const char *resourceTag,
int32_t rowIndex,
int32_t columnIndex,
UErrorCode& err) const
{
if(U_FAILURE(err))
return NULL;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == String2dList::getStaticClassID()) {
String2dList *list = (String2dList*)data;
// Casting to unsigned turns a signed value into a large unsigned
// value. This allows us to do one comparison to check that 0 <=
// index < count, instead of two separate comparisons for each
// index check.
if(((uint32_t)rowIndex) < (uint32_t)(list->fRowCount)
&& ((uint32_t)columnIndex) < (uint32_t)(list->fColCount)) {
return &(list->fStrings[rowIndex][columnIndex]);
}
}
err = U_MISSING_RESOURCE_ERROR;
return NULL;
}
void
ResourceBundle::getTaggedArrayItem( const char *resourceTag,
const UnicodeString& itemTag,
UnicodeString& theArrayItem,
UErrorCode& err) const
{
if(U_FAILURE(err))
return;
const UnicodeString* temp = getTaggedArrayItem(resourceTag, itemTag, err);
if(U_SUCCESS(err))
theArrayItem = *temp;
}
const UnicodeString*
ResourceBundle::getTaggedArrayItem( const char *resourceTag,
const UnicodeString& itemTag,
UErrorCode& err) const
{
if(U_FAILURE(err))
return NULL;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(data != 0
&& data->getDynamicClassID() == TaggedList::getStaticClassID()) {
const UnicodeString* s = ((TaggedList*)data)->get(itemTag);
if(s != 0)
return s;
}
err = U_MISSING_RESOURCE_ERROR;
return NULL;
}
extern "C" void
T_ResourceBundle_getTaggedArrayUChars(const ResourceBundle* bundle,
const char *resourceTag,
UChar const** itemTags,
UChar const** items,
int32_t maxItems,
int32_t* numItems,
UErrorCode* err)
{
// this function is here solely because there seems to be no way to
// declare an extern "C" function as a friend of a class. So we
// have a function with ordinary C++ linkage that is a friend of
// ResourceBundle and does the work, and a hidden method with C
// linkage that calls it and is used by the C wrappers. Disgusting,
// isn't it? This was all rtg's idea. --jf 12/16/98
getTaggedArrayUCharsImplementation(bundle, resourceTag,
itemTags, items, maxItems,
*numItems, *err);
}
void
getTaggedArrayUCharsImplementation( const ResourceBundle* bundle,
const char *resourceTag,
UChar const** itemTags,
UChar const** items,
int32_t maxItems,
int32_t& numItems,
UErrorCode& err)
{
// this is here solely to support the C implementation of
// ResourceBundle. This function isn't defined as part of the API;
// The C wrappers know it's here and define it on their own. --jf
// 12/16/98
if(U_FAILURE(err))
return;
const ResourceBundleData* data = bundle->getDataForTag(resourceTag, err);
if(U_FAILURE(err) || data == 0
|| data->getDynamicClassID() != TaggedList::getStaticClassID()) {
err = U_MISSING_RESOURCE_ERROR;
return;
}
UHashtable* forEnumerationValues = ((TaggedList*)data)->fHashtableValues;
void* value;
numItems = 0;
int32_t pos = -1;
while(value = uhash_nextElement(forEnumerationValues, &pos)) {
if(numItems < maxItems) {
itemTags[numItems] =
((const UnicodeString*)uhash_get(((TaggedList*)data)->fHashtableKeys,
numItems+1))->getUChars();
items[numItems] = ((const UnicodeString*)value)->getUChars();
}
numItems++;
}
}
void
ResourceBundle::getTaggedArray( const char *resourceTag,
UnicodeString*& itemTags,
UnicodeString*& items,
int32_t& numItems,
UErrorCode& err) const
{
if(U_FAILURE(err))
return;
const ResourceBundleData* data = getDataForTag(resourceTag, err);
if(U_FAILURE(err) || data == 0
|| data->getDynamicClassID() != TaggedList::getStaticClassID()) {
err = U_MISSING_RESOURCE_ERROR;
return;
}
// go through the resource once and count how many items there are
numItems = uhash_size(((TaggedList*)data)->fHashtableValues);
// now create the string arrays and go through the hash table again, this
// time copying the keys and values into the string arrays
itemTags = new UnicodeString[numItems];
items = new UnicodeString[numItems];
UHashtable* forEnumerationValues = ((TaggedList*)data)->fHashtableValues;
void* value;
numItems = 0;
int32_t pos = -1;
while(value = uhash_nextElement(forEnumerationValues, &pos)) {
itemTags[numItems] =
*((const UnicodeString*)uhash_get(((TaggedList*)data)->fHashtableKeys,
numItems+1));
items[numItems] = *((const UnicodeString*)value);
numItems++;
}
}
const char*
ResourceBundle::getVersionNumber() const
{
if(fVersionID == 0) {
// If the version ID has not been built yet, then do so. Retrieve
// the minor version from the file.
UErrorCode status = U_ZERO_ERROR;
UnicodeString minor_version;
getString(kVersionTag, minor_version, status);
// Determine the length of of the final version string. This is
// the length of the major part + the length of the separator
// (==1) + the length of the minor part (+ 1 for the zero byte at
// the end).
int32_t len = uprv_strlen(U_ICU_VERSION);
int32_t minor_len = 0;
if(U_SUCCESS(status) && minor_version.length() > 0)
minor_len = minor_version.length();
len += (minor_len > 0) ? minor_len : 1 /*==uprv_strlen(kDefaultMinorVersion)*/;
++len; // Add length of separator
// Allocate the string, and build it up.
// + 1 for zero byte
((ResourceBundle*)this)->fVersionID = new char[1 + len];
uprv_strcpy(fVersionID, U_ICU_VERSION);
uprv_strcat(fVersionID, kVersionSeparator);
if(minor_len > 0) {
minor_version.extract(0, minor_len, fVersionID + len - minor_len);
fVersionID[len] = 0;
}
else {
uprv_strcat(fVersionID, kDefaultMinorVersion);
}
}
return fVersionID;
}
const UnicodeString*
ResourceBundle::listInstalledLocales(const UnicodeString& path,
int32_t& numInstalledLocales)
{
const UnicodeString kDefaultSuffixString = UnicodeString(kDefaultSuffix,"");
const UHashtable* h = getFromCache(PathInfo(path, kDefaultSuffixString),
UnicodeString(kIndexLocaleName,""), fgUserCache);
if(h == 0) {
UErrorCode error = U_ZERO_ERROR;
if(parseIfUnparsed(PathInfo(path, kDefaultSuffixString),
UnicodeString(kIndexFilename,""), fgUserCache,
fgUserVisitedFiles, error)) {
h = getFromCache(PathInfo(path, kDefaultSuffixString),
UnicodeString(kIndexLocaleName,""), fgUserCache);
}
}
if(h != 0) {
UnicodeString ukIndexTag = UnicodeString(kIndexTag,"");
ResourceBundleData *data =
(ResourceBundleData*) uhash_get(h, ukIndexTag.hashCode() & 0x7FFFFFFF);
if(data != 0
&& data->getDynamicClassID() == StringList::getStaticClassID()) {
numInstalledLocales = ((StringList*)data)->fCount;
return ((StringList*)data)->fStrings;
}
}
numInstalledLocales = 0;
return 0;
}
extern "C" const UnicodeString**
T_ResourceBundle_listInstalledLocales(const char* path,
int32_t* numInstalledLocales)
{
// this is here solely to support the C implementation of Locale.
// This function isn't defined as part of the API; T_Locale knows
// it's here and defines it on its own. --rtg 11/28/98
return listInstalledLocalesImplementation(path, numInstalledLocales);
}
const UnicodeString**
listInstalledLocalesImplementation(const char* path,
int32_t* numInstalledLocales)
{
// this function is here solely because there seems to be no way to
// declare an extern "C" function as a friend of a class. So we
// have a function with ordinary C++ linkage that is a friend of
// ResourceBundle and does the work, and a hidden method with C
// linkage that calls it and is used by the C implementation of
// Locale. Disgusting, isn't it? --rtg 11/30/98
const UnicodeString* array = (ResourceBundle::listInstalledLocales(UnicodeString(path,""), *numInstalledLocales));
const UnicodeString** arrayOfPtrs = (const UnicodeString**) new UnicodeString*[*numInstalledLocales];
for(int i = 0; i < *numInstalledLocales; i++)
arrayOfPtrs[i] = &array[i];
return arrayOfPtrs;
}
int32_t
T_ResourceBundle_countArrayItemsImplementation(const ResourceBundle* resourceBundle,
const char* resourceKey,
UErrorCode& err)
{
if(U_FAILURE(err))
return 0;
if(!resourceKey) {
err = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
const ResourceBundleData* data = resourceBundle->getDataForTag(resourceKey,
err);
if(U_FAILURE(err))
return 0;
UClassID rbkeyClassID = data->getDynamicClassID();
int32_t numItems = 0;
if(rbkeyClassID == StringList::getStaticClassID()) {
numItems = ((StringList*)data)->fCount;
}
else if(rbkeyClassID == TaggedList::getStaticClassID()) {
numItems = uhash_size(((TaggedList*)data)->fHashtableValues);
}
else if(rbkeyClassID == String2dList::getStaticClassID()) {
numItems = ((String2dList*)data)->fRowCount;
}
else {
err = U_MISSING_RESOURCE_ERROR;
return 0;
}
return numItems;
}
extern "C" int32_t
T_ResourceBundle_countArrayItems(const ResourceBundle* resourceBundle,
const char* resourceKey,
UErrorCode* err)
{
return T_ResourceBundle_countArrayItemsImplementation(resourceBundle,
resourceKey,
*err);
}
/**
* Retrieve a ResourceBundle from the cache. Return NULL if not found.
*/
const UHashtable*
ResourceBundle::getFromCache(const PathInfo& path,
const UnicodeString& localeName,
ResourceBundleCache* fgCache)
{
UnicodeString keyname(path.makeHashkey(localeName));
Mutex lock;
return (const UHashtable*)
uhash_get(fgCache->hashTable, keyname.hashCode() & 0x7FFFFFFF);
}
/**
* Parse a file, storing the resource data in the cache.
*/
void
ResourceBundle::parse(const PathInfo& path,
const UnicodeString& locale,
Handler handler,
void *context,
ResourceBundleCache *fgCache,
UErrorCode& status)
{
FileStream *f;
UnicodeString localeName, realLocale;
UHashtable *data;
if (U_FAILURE(status)) return;
f = path.openFile(locale);
if(f == 0) {
status = U_FILE_ACCESS_ERROR;
return;
}
realLocale = locale;
/* Get the data from the compiled resource bundle file */
data = rb_parse(f, localeName, status);
/* Close the file */
T_FileStream_close(f);
if(U_FAILURE(status)) {
return;
}
/* If an alias file is encountered, parse the new locale file. */
if (data == 0) {
f = path.openFile(localeName);
if(f == 0) {
status = U_FILE_ACCESS_ERROR;
return;
}
data = rb_parse(f, localeName, status);
T_FileStream_close(f);
if(U_FAILURE(status)) {
return;
}
localeName = realLocale;
}
/* Invoke the handler function */
handler(localeName, data, context, fgCache);
}
void
ResourceBundle::addToCache(const UnicodeString& localeName,
UHashtable* hashtable,
void* context,
ResourceBundleCache* fgCache)
{
PathInfo *c = (PathInfo*)context;
UnicodeString keyName(c->makeHashkey(localeName));
UErrorCode err = U_ZERO_ERROR;
Mutex lock;
if(uhash_get(fgCache->hashTable, keyName.hashCode() & 0x7FFFFFFF) == 0) {
uhash_putKey(fgCache->hashTable, keyName.hashCode() & 0x7FFFFFFF,
hashtable, &err);
}
}
const Locale &ResourceBundle::getLocale(void) const
{
return fRealLocale;
}
ResourceBundle::PathInfo::PathInfo()
: fWPrefix(NULL), fWSuffix(NULL)
{}
ResourceBundle::PathInfo::PathInfo(const PathInfo& source)
: fPrefix(source.fPrefix),
fSuffix(source.fSuffix),
fWPrefix(NULL), fWSuffix(NULL)
{
if(source.fWPrefix) {
fWPrefix = new wchar_t[uprv_wcslen(source.fWPrefix)+1];
fWSuffix = new wchar_t[uprv_wcslen(source.fWSuffix)+1];
uprv_wcscpy(fWPrefix, source.fWPrefix);
uprv_wcscpy(fWSuffix, source.fWSuffix);
}
}
ResourceBundle::PathInfo::PathInfo(const UnicodeString& path)
: fPrefix(path),
fWPrefix(NULL),
fWSuffix(NULL)
{}
ResourceBundle::PathInfo::PathInfo(const UnicodeString& path,
const UnicodeString& suffix)
: fPrefix(path),
fSuffix(suffix),
fWPrefix(NULL),
fWSuffix(NULL)
{}
ResourceBundle::PathInfo::PathInfo(const wchar_t* path,
const wchar_t* suffix)
: fPrefix(),
fSuffix(),
fWPrefix(NULL),
fWSuffix(NULL)
{
fWPrefix = new wchar_t[uprv_wcslen(path)+1];
fWSuffix = new wchar_t[uprv_wcslen(suffix)+1];
uprv_wcscpy(fWPrefix, path);
uprv_wcscpy(fWSuffix, suffix);
}
ResourceBundle::PathInfo::~PathInfo()
{
delete [] fWPrefix;
delete [] fWSuffix;
}
ResourceBundle::PathInfo&
ResourceBundle::PathInfo::operator=(const PathInfo& source)
{
if(this != &source) {
wchar_t* tempPref = NULL;
wchar_t* tempSuff = NULL;
if(source.fWPrefix) {
tempPref = new wchar_t[uprv_wcslen(source.fWPrefix)+1];
tempSuff = new wchar_t[uprv_wcslen(source.fWSuffix)+1];
uprv_wcscpy(tempPref, source.fWPrefix);
uprv_wcscpy(tempSuff, source.fWSuffix);
}
delete fWPrefix;
fWPrefix = tempPref;
delete fWSuffix;
fWSuffix = tempSuff;
fPrefix = source.fPrefix;
fSuffix = source.fSuffix;
}
return *this;
}
bool_t
ResourceBundle::PathInfo::fileExists(const UnicodeString& localeName) const
{
FileStream *temp = openFile(localeName);
if(temp) {
T_FileStream_close(temp);
return TRUE;
}
else {
return FALSE;
}
}
UnicodeString
ResourceBundle::PathInfo::makeCacheKey(const UnicodeString& name) const
{
if(fWPrefix) {
UnicodeString key;
size_t prefSize = uprv_wcstombs(NULL, fWPrefix, ((size_t)-1) >> 1);
size_t suffSize = uprv_wcstombs(NULL, fWSuffix, ((size_t)-1) >> 1);
size_t tempSize = uprv_max((int32_t)prefSize, (int32_t)suffSize);
char *temp = new char[tempSize + 1];
tempSize = uprv_wcstombs(temp, fWPrefix, prefSize);
temp[tempSize] = 0;
key += UnicodeString(temp);
key += name;
tempSize = uprv_wcstombs(temp, fWSuffix, suffSize);
temp[tempSize] = 0;
key += UnicodeString(temp);
delete [] temp;
return key;
}
else {
UnicodeString workingName(fPrefix);
workingName += name;
workingName += fSuffix;
return workingName;
}
}
UnicodeString
ResourceBundle::PathInfo::makeHashkey(const UnicodeString& localeName) const
{
if(fWPrefix) {
UnicodeString key(localeName);
key += kSeparator;
size_t prefSize = uprv_wcstombs(NULL, fWPrefix, ((size_t)-1) >> 1);
size_t suffSize = uprv_wcstombs(NULL, fWSuffix, ((size_t)-1) >> 1);
size_t tempSize = uprv_max((int32_t)prefSize, (int32_t)suffSize);
char *temp = new char[tempSize + 1];
tempSize = uprv_wcstombs(temp, fWSuffix, suffSize);
temp[tempSize] = 0;
key += UnicodeString(temp);
key += kSeparator;
tempSize = uprv_wcstombs(temp, fWPrefix, prefSize);
temp[tempSize] = 0;
key += UnicodeString(temp);
delete [] temp;
return key;
}
else {
UnicodeString keyName = localeName;
keyName += kSeparator;
keyName += fSuffix;
keyName += kSeparator;
keyName += fPrefix;
return keyName;
}
}
FileStream*
ResourceBundle::PathInfo::openFile(const UnicodeString& localeName) const
{
if(fWPrefix) {
//use the wide version of fopen in TPlatformUtilities.
int32_t nameSize = localeName.length();
char* temp = new char[nameSize + 1];
localeName.extract(0, nameSize, temp);
temp[nameSize] = 0;
int32_t wideNameLen = uprv_mbstowcs(NULL, temp, nameSize);
wchar_t* wideName = new wchar_t[wideNameLen + 1];
uprv_mbstowcs(wideName, temp, nameSize);
wideName[wideNameLen] = 0;
delete [] temp;
size_t prefLen = uprv_wcslen(fWPrefix);
size_t suffLen = uprv_wcslen(fWSuffix);
int32_t destSize = prefLen + suffLen + wideNameLen;
wchar_t* dest = new wchar_t[destSize + 1];
uprv_wcscpy(dest, fWPrefix);
dest[prefLen] = 0;
uprv_wcscat(dest, wideName);
dest[prefLen + wideNameLen] = 0;
uprv_wcscat(dest, fWSuffix);
dest[destSize] = 0;
int32_t fmodeLen = uprv_mbstowcs(NULL, "rb", 2);
wchar_t* fmode = new wchar_t[fmodeLen + 1];
uprv_mbstowcs(fmode, "rb", 2);
fmode[fmodeLen] = 0;
FileStream* result = T_FileStream_wopen(dest, fmode);
delete [] fmode;
delete [] dest;
delete [] wideName;
return result;
}
else {
//open file using standard char* routines
UnicodeString workingName(makeCacheKey(localeName));
int32_t size = workingName.length();
char* returnVal = new char[size + 1];
workingName.extract(0, size, returnVal, "");
returnVal[size] = 0;
FileStream* result = T_FileStream_open(returnVal, "rb");
delete [] returnVal;
return result;
}
}
const UChar ResourceBundle::PathInfo::kSeparator = 0xF8FF;
//eof