package org.openrosa.client.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.openrosa.client.model.FormDef;
import org.openrosa.client.model.IFormElement;
import org.openrosa.client.model.ItextModel;
import org.openrosa.client.model.OptionDef;
import org.openrosa.client.model.QuestionDef;
import com.extjs.gxt.ui.client.store.ListStore;
import com.google.gwt.core.client.GWT;
/**
* This static objects holds *all* itext data, and is the only
* object that should be used for storage getting/setting itext values.
*
* (It acts as the CONTROLLER and internally as a model for all Itext related
* business)
*
* There are two types of storage going on. One for the internal model
* of the itexts, the other (a ListStore) for GUI use.
* Both are automatically updated/changed when using the methods of this object.
* @author adewinter
*
*/
public class Itext {
private static ListStore<ItextModel> itextRows = new ListStore<ItextModel>();
public static List<ItextLocale> locales = new ArrayList<ItextLocale>();
public static ItextLocale currentLocale = null;
/**
* Cleans out everything in this static Itext object and starts fresh.
*/
public static void reset(){
itextRows = new ListStore<ItextModel>();
locales = new ArrayList<ItextLocale>();
}
/**
* Used to determine if there is /any/ itextData
* @return true if any itext data is preset, false if no itext data exists.
*/
public static boolean hasItext(){
for(ItextLocale locale : locales){
if(locale.hasItext()){
return true;
}
}
return false;
}
/**
* Update the itextModel with the given row.
* Also updates the internal itext model representation
* @param row
* @param oldID - the old Question ID (used for if the ID has been changes by this update)
*/
public static void updateRow(ItextModel row, String oldID){
for(ItextModel r : itextRows.getModels()){
if(r.get("id").equals(oldID)){
for(String k: row.getPropertyNames()){
r.set(k, row.get(k));
}
}
}
updateModel(itextRows);
}
/**
* Returns a row of ONLY the itext values (for all languages) specified by
* the FULL id (ie, in the style of 'id;form' or 'id_hint' where id=textID of the element).
* This row can be used by the GUI.
* e.g.
* getItextValueRow('ID3KAB') -> ItextModel representing: ['ID3KAB','yes','ja','da'] for ID, english, afrikaans
* and russian translations respectively
*
* The order of the row corresponds to the locales list (a static list located in THIS class only)
* @param fullID (FULL ID of the element)
* @return
*/
public static ItextModel getItextValueRow(String fullID){
ItextModel row = new ItextModel();
row.set("id", fullID);
for (ItextLocale language : locales){
String val = language.getTranslation(fullID);
if (val != null){
row.set(language.name, val);
}
}
return row;
}
/**
* Adds a new translation item to the row specified by ID. Also updates the ListStore (for GUI use)
* Will create new everything (row, language, ID) if they don't exist, or just update them if they do.
* @param language
* @param ID
* @param value
*/
public static void addText(String language, String ID, String value){
ItextLocale lang = Itext.getLocale(language);
lang.setTranslation(ID, value);
itextRowsAddText(language,ID,value);
}
/**
* renames a specific Itext ID (including those with special
* forms)
* @param oldID - BASE ID ONLY. DO NOT INCLUDE SPECIAL FORM
* @param newID - BASE ID ONLY. DO NOT INCLUDE SPECIAL FORM
*/
public static void renameID(String oldID, String newID){
for(ItextLocale locale : locales){
locale.renameID(oldID, newID);
}
syncItextRowsToLocale();
}
/**
* Returns a list of special text forms used by this ID
* (across ALL locales). Only returns the actual forms
* (i.e. "long") as opposed to the entire string (i.e. "someID;long").
* Will use a blank string "" for no form (i.e. there's a default Itext value).
* @param baseID
* @return
*/
public static List<String> getAvailableTextForms(String baseID){
if(baseID == null || baseID.isEmpty()){ return new ArrayList<String>(); }
List<String> forms = new ArrayList<String>();
for(ItextLocale locale:locales){
forms.addAll(locale.getAvailableForms(baseID));
String defaultTrans = locale.getDefaultTranslation(baseID);
if(defaultTrans != null && !defaultTrans.isEmpty()){
forms.add(""); //add blank to indicate there's a default val also
}
}
return forms;
}
/**
* Same as <code>getAvailableTextForms()</code> except
* the IDs are fully qualified (i.e. instead of a list of
* "long","short", etc you get "someID;long","someID;short", etc.
* @param baseID
* @return
*/
public static List<String> getFullAvailableTextForms(String baseID){
if(baseID == null || baseID.isEmpty()){ return new ArrayList<String>(); }
List<String> shortList = getAvailableTextForms(baseID);
List<String> longList = new ArrayList<String>();
for(String s:shortList){
if(s.equals("")){
longList.add(baseID);
}else{
longList.add(baseID+";"+s);
}
}
return longList;
}
/**
* Takes in a list of actively used ItextIDs (including those with special forms!)
* and strips away all unused Itext currently in the internal store
* @param usedIDs
*/
public static void removeUnusedItext(List<String> usedIDs){
for(ItextLocale locale:locales){
locale.removeUnusedItext(usedIDs);
}
syncItextRowsToLocale();
}
private static void renameIdInItextRows(String oldID, String newID){
for(ItextModel row: getItextRows().getModels()){
if(hasID((String)row.get("id"), oldID)){
String id = row.remove("id");
id = id.replace(oldID, newID);
row.set("id",id);
}
}
}
static boolean hasID(String key, String testVal){
boolean hasID = key.contains(testVal + ";") ||
key.equals(testVal);
return hasID;
}
private static void itextRowsAddText(String language, String ID, String value){
ItextModel row = itextRows.findModel("id", ID); //gets the first one that matches, but there *should* only ever be one if coder abides by contract of this method
if(row == null){
row = new ItextModel();
row.set("id", ID);
itextRows.add(row);
}
row.set(language, value);
}
/**
* Removes a row specified by ID (full ID with text form if it exists!)
* Updated the internal model as well as the GUI ListStore
* @param id
* @return True if row was successfully removed, False if row does not exist
*/
public static boolean removeRow(String id){
ItextModel row = itextRows.findModel("id", id);
if(row != null){
itextRows.remove(row);
//find entry in each locale corresponding to this id
//and remove it
for(ItextLocale locale : locales){
if(locale.hasID(id)){
locale.removeTranslation(id);
}
}
return true;
}else{
return false;
}
}
/**
* Returns the locale specified by name.
* WARNING: If the locale does not exist, a new one will be created
* @param name
* @return
*/
public static ItextLocale getLocale(String name){
ItextLocale locale = getLocaleNoAdd(name);
return (locale == null) ? addLocale(name) : locale;
}
/**
* Does the same thing as getLocale(String name)
* but without automatically adding a locale if it
* doesn't exist.
* @param name
* @return
*/
private static ItextLocale getLocaleNoAdd(String name){
for(ItextLocale language : locales){
if(language == null){ return null; }
if(language.getName().equals(name))
return language;
}
return null;
}
/**
* Removes the specified locale (updating both the internal model and the ListStore).
*
* If locale does not exist, this method does nothing.
* @param name - The Locale name
* @return
*/
public static void removeLocale(String name){
//First clear locale from locales list
ItextLocale lang = null;
for(ItextLocale language : locales){
if(language.getName().equals(name))
lang = language;
}
if(lang != null){
locales.remove(lang);
}
//loop through all itextrows and remove specified language key-value pair.
for(ItextModel row: itextRows.getModels()){
row.remove(name);
}
}
/**
* Renames a locale.
* @param name
* @return True if locale exists (and rename succesful), else returns false (locale does not exist)
*/
public static boolean renameLocale(String oldName, String newName){
ItextLocale lang = null;
for(ItextLocale language : locales){
if(language.getName().equals(oldName))
lang = language;
}
if(lang != null){
lang.setName(newName);
}else{
return false; //something is wrong.
}
//loop through all itextrows and remove specified language key-value pair.
for(ItextModel row: itextRows.getModels()){
row.set(newName, row.remove(oldName));
}
return (lang != null);
}
/**
* Add a new locale to the internal model (as well as the ListStore (for the GUI)) and
* return it
* Does nothing if the locale already exists!
* @param name - The name of the new language to be added.
* @return
*/
public static ItextLocale addLocale(String name){
if(!localeExists(name)){
ItextLocale language = new ItextLocale(name);
locales.add(language);
//update each row to have a new key-value pair for the language. Init with null value
//assumes ItextRows and Locales are in sync, otherwise we're wiping out existing data.
for(ItextModel row: itextRows.getModels()){
boolean languageAlreadyInRow = row.getPropertyNames().contains(name);
if(!languageAlreadyInRow)
row.set(name, null);
}
return language;
}else{
return Itext.getLocaleNoAdd(name); //should always return a locale (as opposed to null)
}
}
/**
* Checks to see if the locale by the given name already exists in the model.
* (CASE INSENSITIVE)
* @param name
* @return true if exists, false if not
*/
private static boolean localeExists(String name){
for(ItextLocale locale: locales){
if(locale.getName().toLowerCase().equals(name)) return true; //already exists so do nothing.
}
return false;
}
/**
* THIS METHOD REMOVES ALL LOCALES and ITEXTROWS
*/
public static void clearLocales(){
locales = new ArrayList<ItextLocale>();
itextRows = new ListStore<ItextModel>();
}
private static void setItextRows(ListStore<ItextModel> itextRows){
Itext.itextRows = itextRows;
}
/**
* @return the structure of the ListStore<ItextModel> for use in the GUI
*/
public static ListStore<ItextModel> getItextRows(){
syncItextRowsToLocale();
return itextRows;
}
/**
* Sets the default locale to the one specified
* @param localeName
*/
public static void setDefaultLocale(String localeName){
// Loop through all locales and mark them as NOT default
for (ItextLocale locale : locales){
locale.setDefault(false);
}
// then mark only the one specified as default...
getLocale(localeName).setDefault(true);
// ...to ensure that we only ever have one default locale
}
public static ItextLocale getDefaultLocale(){
ItextLocale defLocale = null;
if(locales.size() == 0){
getLocale("en");
}
for (ItextLocale locale : locales){
if(locale.isDefault()) defLocale = locale;
}
//if no default locale is found set the first one as default and continue
if(defLocale==null){
defLocale = locales.get(0);
setDefaultLocale(defLocale.name);
}
return defLocale;
}
/**
* Takes in a liststore of itext rows
* and updates the internal model + internal row store (in the
* event that the one passed and the one stored here are different)
* @param rows
*/
public static void updateModel(ListStore<ItextModel> rows){
//for the ListStore
//actually we'll just switch the pointer to point to this new ListStore, it's
//computationally less expensive and achieves the same goal
itextRows = rows;
itextRows.commitChanges();
for (ItextModel row : itextRows.getModels()){
//first go by row
String id = (String)row.get("id");
if(id == null){
itextRows.remove(row);
GWT.log("Removing row from Itextrows as it does not have an ID(?)");
continue; //Don't really want to store something against a null key
}
for (ItextLocale locale : locales){
//then by column
locale.setTranslation(id,(String)row.get(locale.name));
}
}
}
/**
* Causes the ItextRows to have the same data as
* that stored in the Locales list.
*/
private static void syncItextRowsToLocale(){
setItextRows(new ListStore<ItextModel>());
for(ItextLocale locale : locales){
for(String id: locale.getAllFULLIds()){
itextRowsAddText(locale.getName(),id,locale.getTranslation(id));
}
}
}
/**
* Set's the itext's idea of what is the 'current' locale to the one specified.
* NB: If the locale does not exist in the internal store already it will be automatically added!
* @param locale
*/
public static void setCurrentLocale(ItextLocale locale){
for(ItextLocale pLocale: locales){
if(pLocale.name.equals(locale.name)){
currentLocale = locale;
return;
}
}
//if we're here, then the locale doesn't exist in the Locales List, so add it
addLocale(locale);
setCurrentLocale(locale); //recursive call
}
/**
* Adds the given locale to the internal itext model.
* NB: if a locale by that name already exists in the model it will be overwritten!
*
* Updates the itextRows as well.
* @param locale
*/
public static void addLocale(ItextLocale locale){
int oldLocaleIndex = -1;
for(int i=0;i<locales.size();i++){
if(locales.get(i).name.toLowerCase().equals(locale.name.toLowerCase())){
oldLocaleIndex = i;
}
}
//There's already a locale by this name, so replace it
if(oldLocaleIndex != -1){
locales.remove(oldLocaleIndex);
locales.add(oldLocaleIndex, locale);
}else{
locales.add(locale);
}
}
public static String getDisplayText(IFormElement elementDef){
String displayText = getDefaultIText(elementDef.getItextId());
if(displayText != null && !displayText.isEmpty()){
return displayText;
}else{
displayText = elementDef.getDisplayText();
if((displayText == null || displayText.isEmpty()) && !elementDef.hasUINode()){
return "DATA NODE. ID: "+elementDef.getQuestionID();
}
return displayText;
}
}
private static String getDefaultIText(String ID){
ItextLocale defLocale = Itext.getDefaultLocale();
String dText = "";
if(defLocale != null){
dText = defLocale.getLongTranslation(ID);
if(dText == null || dText.isEmpty()){
dText = defLocale.getShortTranslation(ID);
}
if(dText == null || dText.isEmpty()){
dText = defLocale.getDefaultTranslation(ID);
}
if(dText == null || dText.isEmpty()){
//give up
dText = "";
}
}
return dText;
}
}