//*********************************************************************************
// class tig.TigBundle
// Software released under the General Public License (version 2 or later), available at
// http://www.gnu.org/copyleft/gpl.html
//*********************************************************************************
package tig;
import tig.GeneralConstants;
import java.util.*;
import java.io.*;
/**********************************************************************************
This class can be used instead of <CODE>ResourceBundle</CODE>. It has been written for these reasons :
<LI>Permit to choose the place where the file containing the bundle (in general a '.properties') is stored ;</LI>
<LI>Put the Strings of different languages in a same file.</LI>
<LI>Avoid having Exceptions thrown when a key can't be found (the {@link #getString(String)} method
returns a empty string if a key can't be found in the bundle).</LI>
<H2>Usage</H2>
<LI>As with the standard API, you put your internationalized strings in bundle files (which don't
have to be suffixed by '.properties'). The difference is that all the strings related to the same display are located in the same file.</LI>
<LI>You fill the bundle files with the standard syntax ("key = value")</LI>
<LI>Like in the standard mechanism, languages, countries and variants are handled.</LI>
<LI>The language and the locale are identified by the suffixes of the key ; the syntax is :
<BR><CODE>myKey_country_locale_variant</CODE> (ex : <CODE>myKey_en_US</CODE>).
</LI>
<BR><BR>The constructors of <CODE>TigBundle</CODE> only store the necessary strings (corresponding to the <CODE>Locale</CODE> specified in the constructor).
<BR><BR><LI><B>WARNING</B> : this implementation does NOT permit to have underscores ( _ )in the names of the keys.
<BR>Example : if you internationalize with French and English, you can have in your file :
<PRE>
myKey_en = myEnglishValue
myKey_fr = myFrenchValue
</PRE>
But the following example is not valid, because of underscores :
<PRE>
my_Key_en = myEnglishValue
my_Key_fr = myFrenchValue
</PRE>
</LI>
<LI>Typical usage :
<PRE>
TigBundle myBundle = new TigBundle("C:\\data\\MyBundle.properties", new Locale("en", "US"));
String str = myBundle.getString("myKey");
</PRE>
</LI>
<LI>Alternative usage :
<BR>In the main constructor ({@link #TigBundle(String, Locale)}), a <CODE>java.io.IOException</CODE> is thrown if the resource file can't be loaded. An other constructor is provided to be able to load "manually" the bundle : ({@link #TigBundle(Properties, Locale)}).
<PRE>
Properties p = new Properties();
p.setProperty("myKey_fr", "myFrenchValue");
p.setProperty("myKey", "myEnglishValue");
TigBundle myBundle = new TigBundle(p, new Locale("en", "US"));
String str = myBundle.getString("myKey");
</PRE>
</LI>
In this alternative usage
<H2>Retrieval mechanism</H2>
When <CODE>getString("myKey")</CODE> is called, it tries to find, in this order :
<LI><CODE>"myKey_country_locale_variant"</CODE></LI>
<LI><CODE>"myKey_country_locale"</CODE></LI>
<LI><CODE>"myKey_country"</CODE></LI>
<LI><CODE>"myKey"</CODE></LI>
<BR>If nothing is found, it returns a empty string.
<BR><BR>This permits to avoid repeating strings shared by several language, and have default values :
<BR>Imagine you handle Spanish, French and English, and that the word "OK" should be expressed by "OK" in French and English, and "Oke" in spanish. You would put in the bundle :
<PRE>
OK_sp = Oke
OK = OK
</PRE>
@author Thierry Graff
@history oct 13 2001 : Creation
@history oct 16 2001 : Implementation of basic features
@history jan 08 2002 : Wrote class javadoc
@todo Find a mechanism to load only the useful strings in memory (in constructor)
@todo Test this clas using variants
**********************************************************************************/
public class TigBundle implements GeneralConstants{
//=================================================================================
// INSTANCE VARIABLES
//=================================================================================
/** Internal representation of the bundle. */
private Properties _data;
//=================================================================================
// CONSTANTS
//=================================================================================
/** Separator used in the bundle files to separate the key with the Locale indicators. */
private static final String SEP = "_";
//=================================================================================
// CONSTRUCT0RS
//=================================================================================
//********************************************
/** Creates an bundle from a Propetries. */
public TigBundle(Properties p, Locale locale){
fillData(p, locale);
}
//********************************************
/** Constructor to use in general.
<BR>When called, the data are loaded from the specified file.
@param pathName Absolute path of the file containing the resources.
@param locale Locale of this bundle.
@throws RuntimeException If parameter 'locale' is null or if its language is not set (null or empty string).
*/
public TigBundle(String pathName, Locale locale) throws IOException{
// parameter checking
if (locale == null || locale.getLanguage() == null || locale.getLanguage().equals(BLANK))
throw new IllegalArgumentException("Incorrect 'locale' parameter");
Properties tmpData = new Properties(); // contains ALL the Strings of the bundle
try{
tmpData.load(new FileInputStream(new File(pathName)));
}
catch(IOException ioe){ throw ioe; }
fillData(tmpData, locale);
}// end TigBundle()
//=================================================================================
// PUBLIC METHODS
//=================================================================================
//***************** getString() ***************************
/** Returns the value corresponding to parameter 'key' (see details of method).
<BR>If the corresponding key does not exist, returns an empty String.
<BR>The search order for a corresponding key is similar as what is done in the standard API :
<BR>If the value of parameter 'key' is '<CODE>theKey</CODE>' and the <CODE>Locale</CODE> used to build
this bundle contains a language 'la', a country 'co' and a variant 'va', will try to find a key, the
following keys will be searched in the file :
<LI><CODE>theKey_la_co_va</CODE></LI>
<LI><CODE>theKey_la_co</CODE></LI>
<LI><CODE>theKey_la</CODE></LI>
<LI><CODE>theKey</CODE></LI>
<BR>If none of these keys are found, <B>returns an empty String</B> (doesn't throw an Exception as in Sun's API).
@param key The key corresponding to the String to retrieve.
*/
public String getString(String key){
String strRes = _data.getProperty(key);
if(strRes == null) return BLANK;
else return strRes;
}// end getString()
/** Returns the <CODE>Properties</CODE> used to internally store the data. */
public Properties getData(){ return _data;}
//=================================================================================
// PRIVATE METHODS
//=================================================================================
/** Builds _data from 'tmpData', removing the Strings that don't correspond to the Locale used to build this bundle.
All constructors MUST call this method, as it builds _data and the _suffix strings used in getString().
*/
private void fillData(Properties tmpData, Locale locale){
_data = new Properties();
// Build the strings representing the possible suffixes.
// suffix[0] is the best, then suffix[1], then suffix[2], then BLANK
// They may remain null.
final int NB_SUFFIX = 4;
String[] suffix = new String[NB_SUFFIX];
suffix[3] = BLANK;
if(!locale.getLanguage().equals(BLANK)) suffix[2] = SEP + locale.getLanguage(); // = _lang
if(!locale.getCountry().equals(BLANK)) suffix[1] = suffix[2] + SEP + locale.getCountry(); // = _lang_country
if(!locale.getVariant().equals(BLANK)) suffix[0] = suffix[1] + SEP + locale.getVariant(); // = _lang_country_variant
// Get the keys, sorted.
Object[] keys = tmpData.keySet().toArray();
Arrays.sort(keys);
// Here, "root" means the part of the key without its suffix
// As the array is sorted, all the keys having the same root follow each other.
String curKey, curRoot;
int len = keys.length;
int begin;
int i, j, k;
String[] sameRoots;
// Loop on the keys and build _data from the useful ones
i = 0;
while(i < len){
curKey = (String)keys[i]; // get the current Key ...
curRoot = getRoot(curKey); // ... and its root
// count the keys having the current root
begin = i; // to retain the current value of i
while (i < len && getRoot((String)keys[i]).equals(curRoot)) i++; while (i < len && getRoot((String)keys[i]).equals(curRoot)) i++;
// allocate an array with the keys having the same root
sameRoots = new String[i - begin];
// loop on this array to retain the best key
jLoop:
for (j = 0; j < NB_SUFFIX; j++){
if(suffix[j] != null){
for(k = 0; k < sameRoots.length; k++){
if(((String)keys[begin + k]).equals(curRoot + suffix[j])){
_data.setProperty(curRoot, tmpData.getProperty((String)keys[begin + k]));
break jLoop;
}
} // end for k
}// end if(suffix[j] != null)
}// end for j
}// end while(i < len)
}// end fillData()
/** Returns the ""root" of a key (the part not depending on locale considerations). */
private static String getRoot(String key){
String root;
int sepIdx = key.indexOf(SEP);
if(sepIdx == -1) root = key;
else root = key.substring(0, sepIdx);
return root;
}
//=================================================================================
//=================================================================================
// TESTS
//=================================================================================
//=================================================================================
// **************** For tests only ****************
//cd bin
//D:\Programs\java\jdk1.4\bin\java -classpath .;bin tig.TigBundle
public static void main(String[] args){
try{
Locale loc = new Locale("fr", "FR");
TigBundle tb = new TigBundle("D:\\b_dvpt\\IF\\java\\tig\\test.lang", loc);
tb.getData().list(System.out);
/*
Locale loc = new Locale("fr", "FR");
TigBundle tb = new TigBundle("D:\\b_dvpt\\IF\\java\\tig\\test.lang", loc);
Properties data = tb.getData();
Properties resultData = new Properties();
// These Strings represent the possible suffixes.
// suffix[0] is the best, then suffix[1], then suffix[2], then BLANK
// They may remain null.
final int NB_SUFFIX = 4;
String[] suffix = new String[NB_SUFFIX];
suffix[3] = BLANK;
if(!loc.getLanguage().equals(BLANK)) suffix[2] = SEP + loc.getLanguage();
if(!loc.getCountry().equals(BLANK)) suffix[1] = suffix[2] + SEP + loc.getCountry();
if(!loc.getVariant().equals(BLANK)) suffix[0] = suffix[1] + SEP + loc.getVariant();
System.out.println("suffix[0] = " + suffix[0]);
System.out.println("suffix[1] = " + suffix[1]);
System.out.println("suffix[2] = " + suffix[2]);
System.out.println("suffix[3] = " + suffix[3]);
// Get the keys, sorted.
Object[] keys = data.keySet().toArray();
Arrays.sort(keys);
// Here, "root" means the part of the key without its suffix
// As the array is sorted, all the keys having the same root follow each other.
String curKey, curRoot;
int len = keys.length;
int begin;
int i, j, k;
String[] sameRoots;
// Loop on the keys and build _data from the useful ones
i = 0;
while(i < len){
curKey = (String)keys[i]; // get the current Key ...
curRoot = getRoot(curKey); // ... and its root
// count the keys having the current root
begin = i; // to retain current value of i
while (i < len && getRoot((String)keys[i]).equals(curRoot)) i++; while (i < len && getRoot((String)keys[i]).equals(curRoot)) i++;
// allocate an array with the keys having the same root
sameRoots = new String[i - begin];
// loop on this array to retain the best key
jLoop:
for (j = 0; j < NB_SUFFIX; j++){
if(suffix[j] != null){
for(k = 0; k < sameRoots.length; k++){
if(((String)keys[begin + k]).equals(curRoot + suffix[j])){
System.out.println("root : " + curRoot + " ===> found " + (String)keys[begin + k]);
resultData.setProperty(curRoot, data.getProperty((String)keys[begin + k]));
break jLoop;
}
} // end for k
}// end if(suffix[j] != null)
}// end for j
}// end while(i < len)
resultData.list(System.out);
*/
}
catch(Exception e){
e.printStackTrace();
}
}// end main
}//end class TigBundle