michael@0: /* michael@0: ********************************************************************** michael@0: * Copyright (C) 1997-2013, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: ********************************************************************** michael@0: * michael@0: * File resbund.cpp michael@0: * michael@0: * Modification History: michael@0: * michael@0: * Date Name Description michael@0: * 02/05/97 aliu Fixed bug in chopLocale. Added scanForLocaleInFile michael@0: * based on code taken from scanForLocale. Added michael@0: * constructor which attempts to read resource bundle michael@0: * from a specific file, without searching other files. michael@0: * 02/11/97 aliu Added UErrorCode return values to constructors. Fixed michael@0: * infinite loops in scanForFile and scanForLocale. michael@0: * Modified getRawResourceData to not delete storage in michael@0: * localeData and resourceData which it doesn't own. michael@0: * Added Mac compatibility #ifdefs for tellp() and michael@0: * ios::nocreate. michael@0: * 03/04/97 aliu Modified to use ExpandingDataSink objects instead of michael@0: * the highly inefficient ostrstream objects. michael@0: * 03/13/97 aliu Rewrote to load in entire resource bundle and store michael@0: * it as a Hashtable of ResourceBundleData objects. michael@0: * Added state table to govern parsing of files. michael@0: * Modified to load locale index out of new file distinct michael@0: * from default.txt. michael@0: * 03/25/97 aliu Modified to support 2-d arrays, needed for timezone data. michael@0: * Added support for custom file suffixes. Again, needed michael@0: * to support timezone data. Improved error handling to michael@0: * detect duplicate tags and subtags. michael@0: * 04/07/97 aliu Fixed bug in getHashtableForLocale(). Fixed handling michael@0: * of failing UErrorCode values on entry to API methods. michael@0: * Fixed bugs in getArrayItem() for negative indices. michael@0: * 04/29/97 aliu Update to use new Hashtable deletion protocol. michael@0: * 05/06/97 aliu Flattened kTransitionTable for HP compiler. michael@0: * Fixed usage of CharString. michael@0: * 06/11/99 stephen Removed parsing of .txt files. michael@0: * Reworked to use new binary format. michael@0: * Cleaned up. michael@0: * 06/14/99 stephen Removed methods taking a filename suffix. michael@0: * 06/22/99 stephen Added missing T_FileStream_close in parse() michael@0: * 11/09/99 weiv Added getLocale(), rewritten constructForLocale() michael@0: * March 2000 weiv complete overhaul. michael@0: ****************************************************************************** michael@0: */ michael@0: michael@0: #include "unicode/utypes.h" michael@0: #include "unicode/resbund.h" michael@0: michael@0: #include "mutex.h" michael@0: #include "uassert.h" michael@0: #include "umutex.h" michael@0: michael@0: #include "uresimp.h" michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: /*----------------------------------------------------------------------------- michael@0: * Implementation Notes michael@0: * michael@0: * Resource bundles are read in once, and thereafter cached. michael@0: * ResourceBundle statically keeps track of which files have been michael@0: * read, so we are guaranteed that each file is read at most once. michael@0: * Resource bundles can be loaded from different data directories and michael@0: * will be treated as distinct, even if they are for the same locale. michael@0: * michael@0: * Resource bundles are lightweight objects, which have pointers to michael@0: * one or more shared Hashtable objects containing all the data. michael@0: * Copying would be cheap, but there is no copy constructor, since michael@0: * there wasn't one in the original API. michael@0: * michael@0: * The ResourceBundle parsing mechanism is implemented as a transition michael@0: * network, for easy maintenance and modification. The network is michael@0: * implemented as a matrix (instead of in code) to make this even michael@0: * easier. The matrix contains Transition objects. Each Transition michael@0: * object describes a destination node and an action to take before michael@0: * moving to the destination node. The source node is encoded by the michael@0: * index of the object in the array that contains it. The pieces michael@0: * needed to understand the transition network are the enums for node michael@0: * IDs and actions, the parse() method, which walks through the michael@0: * network and implements the actions, and the network itself. The michael@0: * network guarantees certain conditions, for example, that a new michael@0: * resource will not be closed until one has been opened first; or michael@0: * that data will not be stored into a TaggedList until a TaggedList michael@0: * has been created. Nonetheless, the code in parse() does some michael@0: * consistency checks as it runs the network, and fails with an michael@0: * U_INTERNAL_PROGRAM_ERROR if one of these checks fails. If the input michael@0: * data has a bad format, an U_INVALID_FORMAT_ERROR is returned. If you michael@0: * see an U_INTERNAL_PROGRAM_ERROR the transition matrix has a bug in michael@0: * it. michael@0: * michael@0: * Old functionality of multiple locales in a single file is still michael@0: * supported. For this reason, LOCALE names override FILE names. If michael@0: * data for en_US is located in the en.txt file, once it is loaded, michael@0: * the code will not care where it came from (other than remembering michael@0: * which directory it came from). However, if there is an en_US michael@0: * resource in en_US.txt, that will take precedence. There is no michael@0: * limit to the number or type of resources that can be stored in a michael@0: * file, however, files are only searched in a specific way. If michael@0: * en_US_CA is requested, then first en_US_CA.txt is searched, then michael@0: * en_US.txt, then en.txt, then default.txt. So it only makes sense michael@0: * to put certain locales in certain files. In this example, it would michael@0: * be logical to put en_US_CA, en_US, and en into the en.txt file, michael@0: * since they would be found there if asked for. The extreme example michael@0: * is to place all locale resources into default.txt, which should michael@0: * also work. michael@0: * michael@0: * Inheritance is implemented. For example, xx_YY_zz inherits as michael@0: * follows: xx_YY_zz, xx_YY, xx, default. Inheritance is implemented michael@0: * as an array of hashtables. There will be from 1 to 4 hashtables in michael@0: * the array. michael@0: * michael@0: * Fallback files are implemented. The fallback pattern is Language michael@0: * Country Variant (LCV) -> LC -> L. Fallback is first done for the michael@0: * requested locale. Then it is done for the default locale, as michael@0: * returned by Locale::getDefault(). Then the special file michael@0: * default.txt is searched for the default locale. The overall FILE michael@0: * fallback path is LCV -> LC -> L -> dLCV -> dLC -> dL -> default. michael@0: * michael@0: * Note that although file name searching includes the default locale, michael@0: * once a ResourceBundle object is constructed, the inheritance path michael@0: * no longer includes the default locale. The path is LCV -> LC -> L michael@0: * -> default. michael@0: * michael@0: * File parsing is lazy. Nothing is parsed unless it is called for by michael@0: * someone. So when a ResourceBundle for xx_YY_zz is constructed, michael@0: * only that locale is parsed (along with anything else in the same michael@0: * file). Later, if the FooBar tag is asked for, and if it isn't michael@0: * found in xx_YY_zz, then xx_YY.txt will be parsed and checked, and michael@0: * so forth, until the chain is exhausted or the tag is found. michael@0: * michael@0: * Thread-safety is implemented around caches, both the cache that michael@0: * stores all the resouce data, and the cache that stores flags michael@0: * indicating whether or not a file has been visited. These caches michael@0: * delete their storage at static cleanup time, when the process michael@0: * quits. michael@0: * michael@0: * ResourceBundle supports TableCollation as a special case. This michael@0: * involves having special ResourceBundle objects which DO own their michael@0: * data, since we don't want large collation rule strings in the michael@0: * ResourceBundle cache (these are already cached in the michael@0: * TableCollation cache). TableCollation files (.ctx files) have the michael@0: * same format as normal resource data files, with a different michael@0: * interpretation, from the standpoint of ResourceBundle. .ctx files michael@0: * are loaded into otherwise ordinary ResourceBundle objects. They michael@0: * don't inherit (that's implemented by TableCollation) and they own michael@0: * their data (as mentioned above). However, they still support michael@0: * possible multiple locales in a single .ctx file. (This is in michael@0: * practice a bad idea, since you only want the one locale you're michael@0: * looking for, and only one tag will be present michael@0: * ("CollationElements"), so you don't need an inheritance chain of michael@0: * multiple locales.) Up to 4 locale resources will be loaded from a michael@0: * .ctx file; everything after the first 4 is ignored (parsed and michael@0: * deleted). (Normal .txt files have no limit.) Instead of being michael@0: * loaded into the cache, and then looked up as needed, the locale michael@0: * resources are read straight into the ResourceBundle object. michael@0: * michael@0: * The Index, which used to reside in default.txt, has been moved to a michael@0: * new file, index.txt. This file contains a slightly modified format michael@0: * with the addition of the "InstalledLocales" tag; it looks like: michael@0: * michael@0: * Index { michael@0: * InstalledLocales { michael@0: * ar michael@0: * .. michael@0: * zh_TW michael@0: * } michael@0: * } michael@0: */ michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ResourceBundle) michael@0: michael@0: ResourceBundle::ResourceBundle(UErrorCode &err) michael@0: :UObject(), fLocale(NULL) michael@0: { michael@0: fResource = ures_open(0, Locale::getDefault().getName(), &err); michael@0: } michael@0: michael@0: ResourceBundle::ResourceBundle(const ResourceBundle &other) michael@0: :UObject(other), fLocale(NULL) michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: michael@0: if (other.fResource) { michael@0: fResource = ures_copyResb(0, other.fResource, &status); michael@0: } else { michael@0: /* Copying a bad resource bundle */ michael@0: fResource = NULL; michael@0: } michael@0: } michael@0: michael@0: ResourceBundle::ResourceBundle(UResourceBundle *res, UErrorCode& err) michael@0: :UObject(), fLocale(NULL) michael@0: { michael@0: if (res) { michael@0: fResource = ures_copyResb(0, res, &err); michael@0: } else { michael@0: /* Copying a bad resource bundle */ michael@0: fResource = NULL; michael@0: } michael@0: } michael@0: michael@0: ResourceBundle::ResourceBundle(const char* path, const Locale& locale, UErrorCode& err) michael@0: :UObject(), fLocale(NULL) michael@0: { michael@0: fResource = ures_open(path, locale.getName(), &err); michael@0: } michael@0: michael@0: michael@0: ResourceBundle& ResourceBundle::operator=(const ResourceBundle& other) michael@0: { michael@0: if(this == &other) { michael@0: return *this; michael@0: } michael@0: if(fResource != 0) { michael@0: ures_close(fResource); michael@0: fResource = NULL; michael@0: } michael@0: if (fLocale != NULL) { michael@0: delete fLocale; michael@0: fLocale = NULL; michael@0: } michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: if (other.fResource) { michael@0: fResource = ures_copyResb(0, other.fResource, &status); michael@0: } else { michael@0: /* Copying a bad resource bundle */ michael@0: fResource = NULL; michael@0: } michael@0: return *this; michael@0: } michael@0: michael@0: ResourceBundle::~ResourceBundle() michael@0: { michael@0: if(fResource != 0) { michael@0: ures_close(fResource); michael@0: } michael@0: if(fLocale != NULL) { michael@0: delete(fLocale); michael@0: } michael@0: } michael@0: michael@0: ResourceBundle * michael@0: ResourceBundle::clone() const { michael@0: return new ResourceBundle(*this); michael@0: } michael@0: michael@0: UnicodeString ResourceBundle::getString(UErrorCode& status) const { michael@0: int32_t len = 0; michael@0: const UChar *r = ures_getString(fResource, &len, &status); michael@0: return UnicodeString(TRUE, r, len); michael@0: } michael@0: michael@0: const uint8_t *ResourceBundle::getBinary(int32_t& len, UErrorCode& status) const { michael@0: return ures_getBinary(fResource, &len, &status); michael@0: } michael@0: michael@0: const int32_t *ResourceBundle::getIntVector(int32_t& len, UErrorCode& status) const { michael@0: return ures_getIntVector(fResource, &len, &status); michael@0: } michael@0: michael@0: uint32_t ResourceBundle::getUInt(UErrorCode& status) const { michael@0: return ures_getUInt(fResource, &status); michael@0: } michael@0: michael@0: int32_t ResourceBundle::getInt(UErrorCode& status) const { michael@0: return ures_getInt(fResource, &status); michael@0: } michael@0: michael@0: const char *ResourceBundle::getName(void) const { michael@0: return ures_getName(fResource); michael@0: } michael@0: michael@0: const char *ResourceBundle::getKey(void) const { michael@0: return ures_getKey(fResource); michael@0: } michael@0: michael@0: UResType ResourceBundle::getType(void) const { michael@0: return ures_getType(fResource); michael@0: } michael@0: michael@0: int32_t ResourceBundle::getSize(void) const { michael@0: return ures_getSize(fResource); michael@0: } michael@0: michael@0: UBool ResourceBundle::hasNext(void) const { michael@0: return ures_hasNext(fResource); michael@0: } michael@0: michael@0: void ResourceBundle::resetIterator(void) { michael@0: ures_resetIterator(fResource); michael@0: } michael@0: michael@0: ResourceBundle ResourceBundle::getNext(UErrorCode& status) { michael@0: UResourceBundle r; michael@0: michael@0: ures_initStackObject(&r); michael@0: ures_getNextResource(fResource, &r, &status); michael@0: ResourceBundle res(&r, status); michael@0: if (U_SUCCESS(status)) { michael@0: ures_close(&r); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: UnicodeString ResourceBundle::getNextString(UErrorCode& status) { michael@0: int32_t len = 0; michael@0: const UChar* r = ures_getNextString(fResource, &len, 0, &status); michael@0: return UnicodeString(TRUE, r, len); michael@0: } michael@0: michael@0: UnicodeString ResourceBundle::getNextString(const char ** key, UErrorCode& status) { michael@0: int32_t len = 0; michael@0: const UChar* r = ures_getNextString(fResource, &len, key, &status); michael@0: return UnicodeString(TRUE, r, len); michael@0: } michael@0: michael@0: ResourceBundle ResourceBundle::get(int32_t indexR, UErrorCode& status) const { michael@0: UResourceBundle r; michael@0: michael@0: ures_initStackObject(&r); michael@0: ures_getByIndex(fResource, indexR, &r, &status); michael@0: ResourceBundle res(&r, status); michael@0: if (U_SUCCESS(status)) { michael@0: ures_close(&r); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: UnicodeString ResourceBundle::getStringEx(int32_t indexS, UErrorCode& status) const { michael@0: int32_t len = 0; michael@0: const UChar* r = ures_getStringByIndex(fResource, indexS, &len, &status); michael@0: return UnicodeString(TRUE, r, len); michael@0: } michael@0: michael@0: ResourceBundle ResourceBundle::get(const char* key, UErrorCode& status) const { michael@0: UResourceBundle r; michael@0: michael@0: ures_initStackObject(&r); michael@0: ures_getByKey(fResource, key, &r, &status); michael@0: ResourceBundle res(&r, status); michael@0: if (U_SUCCESS(status)) { michael@0: ures_close(&r); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: ResourceBundle ResourceBundle::getWithFallback(const char* key, UErrorCode& status){ michael@0: UResourceBundle r; michael@0: ures_initStackObject(&r); michael@0: ures_getByKeyWithFallback(fResource, key, &r, &status); michael@0: ResourceBundle res(&r, status); michael@0: if(U_SUCCESS(status)){ michael@0: ures_close(&r); michael@0: } michael@0: return res; michael@0: } michael@0: UnicodeString ResourceBundle::getStringEx(const char* key, UErrorCode& status) const { michael@0: int32_t len = 0; michael@0: const UChar* r = ures_getStringByKey(fResource, key, &len, &status); michael@0: return UnicodeString(TRUE, r, len); michael@0: } michael@0: michael@0: const char* michael@0: ResourceBundle::getVersionNumber() const michael@0: { michael@0: return ures_getVersionNumberInternal(fResource); michael@0: } michael@0: michael@0: void ResourceBundle::getVersion(UVersionInfo versionInfo) const { michael@0: ures_getVersion(fResource, versionInfo); michael@0: } michael@0: michael@0: static UMutex gLocaleLock = U_MUTEX_INITIALIZER; michael@0: const Locale &ResourceBundle::getLocale(void) const { michael@0: Mutex lock(&gLocaleLock); michael@0: if (fLocale != NULL) { michael@0: return *fLocale; michael@0: } michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: const char *localeName = ures_getLocaleInternal(fResource, &status); michael@0: ResourceBundle *ncThis = const_cast(this); michael@0: ncThis->fLocale = new Locale(localeName); michael@0: return ncThis->fLocale != NULL ? *ncThis->fLocale : Locale::getDefault(); michael@0: } michael@0: michael@0: const Locale ResourceBundle::getLocale(ULocDataLocaleType type, UErrorCode &status) const michael@0: { michael@0: return ures_getLocaleByType(fResource, type, &status); michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: //eof