home *** CD-ROM | disk | FTP | other *** search
- /*
- *******************************************************************************
- * *
- * 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()
- *******************************************************************************
- */
-
- #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 <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 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),
- fRealLocaleID(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;
-
- error = U_ZERO_ERROR;
-
- locale.getName(fRealLocaleID);
-
- // if the locale we were passed is Locale("", "", ""), that, by
- // convention, refers to the root locale (default.txt), even when
- // the system default locale is something else (otherwise there's no
- // way to get to it). We can accomplish this by changing the locale
- // name to "default" here. I'm not sure this is the best way to do
- // this, but it's simple and it works.
- if(fRealLocaleID.size() == 0)
- fRealLocaleID = kDefaultLocaleName;
-
- for(i = 1; i < kDataCount; ++i) {
- fData[i] = 0;
- fDataStatus[i] = U_INTERNAL_PROGRAM_ERROR;
- fLoaded[i] = FALSE;
- }
-
- UnicodeString returnedLocale;
- error = U_ZERO_ERROR;
- fData[0] = getHashtableForLocale(fRealLocaleID, returnedLocale, error);
- fLoaded[0] = TRUE;
- fDataStatus[0] = U_ZERO_ERROR;
- if(U_SUCCESS(error))
- fRealLocaleID = returnedLocale;
-
- fLocaleIterator = new LocaleFallbackIterator(fRealLocaleID,
- 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);
- }
- }
-
- 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
-