/* ******************************************************************************* * * * COPYRIGHT: * * (C) Copyright Taligent, Inc., 1997 * * (C) Copyright International Business Machines Corporation, 1997-1999 * * Licensed Material - Program-Property of IBM - All Rights Reserved. * * US Government Users Restricted Rights - Use, duplication, or disclosure * * restricted by GSA ADP Schedule Contract with IBM Corp. * * * ******************************************************************************* * * 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 "resbund.h" #include "mutex.h" #include "unistrm.h" #include "filestrm.h" #include "cstring.h" #include "uhash.h" #include "rbdata.h" #include "rbread.h" #include #include #include /*----------------------------------------------------------------------------- * 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 UnicodeString 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.size() == 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 << " " << errorName(status) << endl; return TRUE; } void ResourceBundle::LocaleFallbackIterator::chopLocale() { int32_t size = fLocale.size(); 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, 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 = icu_mbstowcs(NULL, kDefaultSuffix, kDefaultSuffixLen); wchar_t* wideName = new wchar_t[wideNameLen + 1]; icu_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.size()!=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 UnicodeString& 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], 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 UnicodeString& 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 = icu_strlen(ICU_VERSION); int32_t minor_len = 0; if(U_SUCCESS(status) && minor_version.size() > 0) minor_len = minor_version.size(); len += (minor_len > 0) ? minor_len : 1 /*==icu_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]; icu_strcpy(fVersionID, ICU_VERSION); icu_strcat(fVersionID, kVersionSeparator); if(minor_len > 0) { minor_version.extract(0, minor_len, fVersionID + len - minor_len); fVersionID[len] = 0; } else { icu_strcat(fVersionID, kDefaultMinorVersion); } } return fVersionID; } const UnicodeString* ResourceBundle::listInstalledLocales(const UnicodeString& path, int32_t& numInstalledLocales) { const UHashtable* h = getFromCache(PathInfo(path, kDefaultSuffix), kIndexLocaleName, fgUserCache); if(h == 0) { UErrorCode error = U_ZERO_ERROR; if(parseIfUnparsed(PathInfo(path, kDefaultSuffix), kIndexFilename, fgUserCache, fgUserVisitedFiles, error)) { h = getFromCache(PathInfo(path, kDefaultSuffix), kIndexLocaleName, fgUserCache); } } if(h != 0) { UnicodeString ukIndexTag = 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(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; UHashtable *data; if (U_FAILURE(status)) return; f = path.openFile(locale); if(f == 0) { status = U_FILE_ACCESS_ERROR; return; } /* 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; } /* 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[icu_wcslen(source.fWPrefix)+1]; fWSuffix = new wchar_t[icu_wcslen(source.fWSuffix)+1]; icu_wcscpy(fWPrefix, source.fWPrefix); icu_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[icu_wcslen(path)+1]; fWSuffix = new wchar_t[icu_wcslen(suffix)+1]; icu_wcscpy(fWPrefix, path); icu_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[icu_wcslen(source.fWPrefix)+1]; tempSuff = new wchar_t[icu_wcslen(source.fWSuffix)+1]; icu_wcscpy(tempPref, source.fWPrefix); icu_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 = icu_wcstombs(NULL, fWPrefix, ((size_t)-1) >> 1); size_t suffSize = icu_wcstombs(NULL, fWSuffix, ((size_t)-1) >> 1); size_t tempSize = icu_max((int32_t)prefSize, (int32_t)suffSize); char *temp = new char[tempSize + 1]; tempSize = icu_wcstombs(temp, fWPrefix, prefSize); temp[tempSize] = 0; key += UnicodeString(temp); key += name; tempSize = icu_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 = icu_wcstombs(NULL, fWPrefix, ((size_t)-1) >> 1); size_t suffSize = icu_wcstombs(NULL, fWSuffix, ((size_t)-1) >> 1); size_t tempSize = icu_max((int32_t)prefSize, (int32_t)suffSize); char *temp = new char[tempSize + 1]; tempSize = icu_wcstombs(temp, fWSuffix, suffSize); temp[tempSize] = 0; key += UnicodeString(temp); key += kSeparator; tempSize = icu_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.size(); char* temp = new char[nameSize + 1]; localeName.extract(0, nameSize, temp); temp[nameSize] = 0; int32_t wideNameLen = icu_mbstowcs(NULL, temp, nameSize); wchar_t* wideName = new wchar_t[wideNameLen + 1]; icu_mbstowcs(wideName, temp, nameSize); wideName[wideNameLen] = 0; delete [] temp; size_t prefLen = icu_wcslen(fWPrefix); size_t suffLen = icu_wcslen(fWSuffix); int32_t destSize = prefLen + suffLen + wideNameLen; wchar_t* dest = new wchar_t[destSize + 1]; icu_wcscpy(dest, fWPrefix); dest[prefLen] = 0; icu_wcscat(dest, wideName); dest[prefLen + wideNameLen] = 0; icu_wcscat(dest, fWSuffix); dest[destSize] = 0; int32_t fmodeLen = icu_mbstowcs(NULL, "rb", 2); wchar_t* fmode = new wchar_t[fmodeLen + 1]; icu_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.size(); 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