/*
*******************************************************************************
*                                                                             *
* 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
