/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.localization;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Observable;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.openflexo.toolbox.FlexoProperties;
import org.openflexo.toolbox.HTMLUtils;
import org.openflexo.toolbox.HasPropertyChangeSupport;
import org.openflexo.toolbox.StringUtils;
/**
* Provides a default implementation for a localized delegate.<br>
* Keys and values are managed and retrieved from language-specific dictionaries stored in a given directory<br>
*
* This class provides also a basic support for new entries management: this software part automatically add entries in all languages for
* all new entries, so provides an efficient and soft localized managing.
*
* @author sylvain
*
*/
public class LocalizedDelegateImpl extends Observable implements LocalizedDelegate {
private static final Logger logger = Logger.getLogger(LocalizedDelegateImpl.class.getPackage().getName());
private LocalizedDelegate parent;
private File _localizedDirectory;
private Hashtable<Language, Properties> _localizedDictionaries;
private boolean automaticSaving = false;
private Vector<Entry> entries;
private Vector<Entry> issuesEntries;
private Vector<Entry> matchingEntries;
public static enum SearchMode {
Contains, BeginsWith, EndsWith
}
public LocalizedDelegateImpl(File localizedDirectory, LocalizedDelegate parent, boolean automaticSaving) {
this.automaticSaving = automaticSaving;
this.parent = parent;
_localizedDirectory = localizedDirectory;
loadLocalizedDictionaries();
}
private Properties getDictionary(Language language) {
Properties dict = _localizedDictionaries.get(language);
if (dict == null) {
dict = createNewDictionary(language);
}
return dict;
}
private Properties createNewDictionary(Language language) {
Properties newDict = new Properties();
_localizedDictionaries.put(language, newDict);
saveDictionary(language, newDict);
return newDict;
}
private File getDictionaryFileForLanguage(Language language) {
return new File(_localizedDirectory, language.getName() + ".dict");
}
private void saveDictionary(Language language, Properties dict) {
File dictFile = getDictionaryFileForLanguage(language);
try {
final FileOutputStream fos = new FileOutputStream(dictFile);
try {
if (!dictFile.exists()) {
dictFile.createNewFile();
}
dict.store(new JavaPropertiesOutputStream(fos), language.getName());
logger.info("Saved " + dictFile.getAbsolutePath());
} catch (IOException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Unable to save file " + dictFile.getAbsolutePath() + " " + e.getClass().getName());
// e.printStackTrace();
}
} finally {
IOUtils.closeQuietly(fos);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private Properties loadDictionary(Language language) {
File dictFile = getDictionaryFileForLanguage(language);
Properties loadedDict = new FlexoProperties();
try {
loadedDict.load(new FileInputStream(dictFile));
} catch (IOException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Unable to load file " + dictFile.getName());
}
}
return loadedDict;
}
private void loadLocalizedDictionaries() {
_localizedDictionaries = new Hashtable<Language, Properties>();
for (Language language : Language.availableValues()) {
File dictFile = getDictionaryFileForLanguage(language);
if (logger.isLoggable(Level.INFO)) {
logger.info("Checking dictionary for language " + language.getName() + " file=" + dictFile.getAbsolutePath());
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Looking for file " + dictFile.getAbsolutePath());
}
if (!dictFile.exists()) {
createNewDictionary(language);
} else {
Properties loadedDict = loadDictionary(language);
_localizedDictionaries.put(language, loadedDict);
}
}
}
private void addEntryInDictionary(Language language, String key, String value, boolean required) {
Properties dict = getDictionary(language);
if (!required && dict.getProperty(key) == null || required) {
if (logger.isLoggable(Level.INFO)) {
logger.info("Adding entry '" + key + "' in " + language + " dictionary, file "
+ getDictionaryFileForLanguage(language).getAbsolutePath());
}
dict.setProperty(key, value);
// saveDictionary(language, dict);
}
}
public void addEntry(String key) {
// Add in all dictionaries, when required
for (Language language : Language.availableValues()) {
addEntryInDictionary(language, key, key, false);
}
entries = null;
setChanged();
notifyObservers();
}
public void removeEntry(String key) {
// Remove from all dictionaries
for (Language language : Language.availableValues()) {
Properties dict = getDictionary(language);
dict.remove(key);
// saveDictionary(language, dict);
}
entries = null;
setChanged();
notifyObservers();
}
public void saveAllDictionaries() {
for (Language language : Language.availableValues()) {
Properties dict = getDictionary(language);
saveDictionary(language, dict);
}
}
@Override
public boolean handleNewEntry(String key, Language language) {
return true;
}
@Override
public String getLocalizedForKeyAndLanguage(String key, Language language) {
if (_localizedDictionaries == null) {
loadLocalizedDictionaries();
}
Properties currentLanguageDict = getDictionary(language);
// String localized = currentLanguageDict.getProperty(key);
return currentLanguageDict.getProperty(key);
/*if (localized == null) {
addEntry(key);
save();
return currentLanguageDict.getProperty(key);
} else {
return localized;
}*/
}
public void setLocalizedForKeyAndLanguage(String key, String value, Language language) {
if (_localizedDictionaries == null) {
loadLocalizedDictionaries();
}
Properties currentLanguageDict = getDictionary(language);
currentLanguageDict.setProperty(key, value);
// saveDictionary(language, currentLanguageDict);
}
public class Entry implements HasPropertyChangeSupport {
private static final String DELETED_PROPERTY = "deleted";
private String key;
private PropertyChangeSupport pcSupport;
public Entry(String aKey) {
key = aKey;
pcSupport = new PropertyChangeSupport(this);
}
@Override
public PropertyChangeSupport getPropertyChangeSupport() {
return pcSupport;
}
public String getEnglish() {
String localized = getLocalizedForKeyAndLanguage(key, Language.ENGLISH);
return localized != null ? localized : key;
// return localizedForKeyAndLanguage(key, Language.ENGLISH);
}
public void setEnglish(String value) {
// System.out.println("setEnglish with " + value);
String oldValue = getEnglish();
setLocalizedForKeyAndLanguage(key, value, Language.ENGLISH);
pcSupport.firePropertyChange("english", oldValue, value);
}
public String getFrench() {
String localized = getLocalizedForKeyAndLanguage(key, Language.FRENCH);
return localized != null ? localized : key;
// return localizedForKeyAndLanguage(key, Language.FRENCH);
}
public void setFrench(String value) {
// System.out.println("setFrench with " + value);
String oldValue = getFrench();
setLocalizedForKeyAndLanguage(key, value, Language.FRENCH);
pcSupport.firePropertyChange("french", oldValue, value);
}
public String getDutch() {
String localized = getLocalizedForKeyAndLanguage(key, Language.DUTCH);
return localized != null ? localized : key;
// return localizedForKeyAndLanguage(key, Language.DUTCH);
}
public void setDutch(String value) {
// System.out.println("setDutch with " + value);
String oldValue = getDutch();
setLocalizedForKeyAndLanguage(key, value, Language.DUTCH);
pcSupport.firePropertyChange("dutch", oldValue, value);
}
public void delete() {
removeEntry(key);
pcSupport.firePropertyChange(DELETED_PROPERTY, false, true);
}
@Override
public String getDeletedProperty() {
return DELETED_PROPERTY;
}
public String getKey() {
return key;
}
public void setKey(String aNewKey) {
String oldKey = key;
for (Language l : Language.availableValues()) {
Properties dict = getDictionary(l);
String oldValue = dict.getProperty(oldKey);
dict.remove(oldKey);
if (oldValue != null) {
dict.setProperty(aNewKey, oldValue);
}
}
key = aNewKey;
entries = null;
}
public boolean hasInvalidValue() {
return !isFrenchValueValid() || !isEnglishValueValid() || !isDutchValueValid();
}
public boolean isFrenchValueValid() {
return isValueValid(getKey(), getFrench());
}
public boolean isEnglishValueValid() {
return isValueValid(getKey(), getEnglish());
}
public boolean isDutchValueValid() {
return isValueValid(getKey(), getDutch());
}
public boolean isValueValid(String aKey, String aValue) {
if (aValue == null || aValue.length() == 0) {
return false;
} // null or empty value is not valid
if (aValue.equals(aKey)) {
return false;
} // not the same value > means not translated
if (aValue.lastIndexOf("_") > -1) {
return false;
} // should not contains UNDERSCORE char
return true;
}
public boolean getIsHTML() {
return getFrench().startsWith("<html>") || getEnglish().startsWith("<html>") || getDutch().startsWith("<html>");
}
public void setIsHTML(boolean flag) {
if (flag) {
setEnglish(addHTMLSupport(getEnglish()));
setFrench(addHTMLSupport(getFrench()));
setDutch(addHTMLSupport(getDutch()));
} else {
setEnglish(removeHTMLSupport(getEnglish()));
setFrench(removeHTMLSupport(getFrench()));
setDutch(removeHTMLSupport(getDutch()));
}
}
private String addHTMLSupport(String value) {
return "<html>" + StringUtils.LINE_SEPARATOR + "<head>" + StringUtils.LINE_SEPARATOR + "</head>" + StringUtils.LINE_SEPARATOR
+ "<body>" + StringUtils.LINE_SEPARATOR + value + StringUtils.LINE_SEPARATOR + "</body>" + StringUtils.LINE_SEPARATOR
+ "</html>";
}
private String removeHTMLSupport(String value) {
return HTMLUtils.convertHTMLToPlainText(HTMLUtils.extractBodyContent(value, true).trim(), true);
/*System.out.println("From " + value);
System.out.println("To" + HTMLUtils.extractSourceFromEmbeddedTag(value));
return HTMLUtils.extractSourceFromEmbeddedTag(value);*/
}
private boolean contains(String s) {
if (s == null) {
return false;
}
if (getKey().indexOf(s) >= 0) {
return true;
}
if (getEnglish().indexOf(s) >= 0) {
return true;
}
if (getFrench().indexOf(s) >= 0) {
return true;
}
if (getDutch().indexOf(s) >= 0) {
return true;
}
return false;
}
private boolean startsWith(String s) {
if (s == null) {
return false;
}
if (getKey().startsWith(s)) {
return true;
}
if (getEnglish().startsWith(s)) {
return true;
}
if (getFrench().startsWith(s)) {
return true;
}
if (getDutch().startsWith(s)) {
return true;
}
return false;
}
private boolean endsWith(String s) {
if (s == null) {
return false;
}
if (getKey().endsWith(s)) {
return true;
}
if (getEnglish().endsWith(s)) {
return true;
}
if (getFrench().endsWith(s)) {
return true;
}
if (getDutch().endsWith(s)) {
return true;
}
return false;
}
}
public Vector<Entry> getEntries() {
if (entries == null) {
if (_localizedDictionaries == null) {
loadLocalizedDictionaries();
}
entries = new Vector<Entry>();
if (_localizedDictionaries.size() > 0) {
Enumeration en = _localizedDictionaries.values().iterator().next().propertyNames();
while (en.hasMoreElements()) {
entries.add(new Entry((String) en.nextElement()));
}
}
Collections.sort(entries, new Comparator<Entry>() {
@Override
public int compare(Entry o1, Entry o2) {
return Collator.getInstance().compare(o1.key, o2.key);
}
});
computeIssuesEntries();
}
return entries;
}
public Vector<Entry> getIssuesEntries() {
if (issuesEntries == null) {
issuesEntries = computeIssuesEntries();
}
return issuesEntries;
}
private Vector<Entry> computeIssuesEntries() {
if (_localizedDictionaries == null) {
loadLocalizedDictionaries();
}
issuesEntries = new Vector<Entry>();
for (Entry e : entries) {
if (e.hasInvalidValue()) {
issuesEntries.add(e);
}
}
return issuesEntries;
}
public Vector<Entry> getMatchingEntries() {
if (matchingEntries == null) {
matchingEntries = new Vector<Entry>();
}
return matchingEntries;
}
protected Vector<Entry> computeMatchingEntries(String text, SearchMode searchMode) {
if (_localizedDictionaries == null) {
loadLocalizedDictionaries();
}
matchingEntries = new Vector<Entry>();
for (Entry e : getEntries()) {
switch (searchMode) {
case Contains:
if (e.contains(text)) {
matchingEntries.add(e);
}
break;
case BeginsWith:
if (e.startsWith(text)) {
matchingEntries.add(e);
}
break;
case EndsWith:
if (e.endsWith(text)) {
matchingEntries.add(e);
}
break;
default:
break;
}
}
return matchingEntries;
}
private Entry getEntry(String key) {
if (key == null) {
return null;
}
for (Entry entry : getEntries()) {
if (key.equals(entry.key)) {
return entry;
}
}
return null;
}
public void save() {
saveAllDictionaries();
}
public void refresh() {
entries = null;
setChanged();
notifyObservers();
}
public Entry addEntry() {
addEntry("key");
return getEntry("key");
}
public void deleteEntry(Entry entry) {
entry.delete();
}
public void searchTranslation(Entry entry) {
if (getParent() != null) {
String englishTranslation = FlexoLocalization.localizedForKeyAndLanguage(parent, entry.key, Language.ENGLISH);
if (entry.key.equals(englishTranslation)) {
englishTranslation = automaticEnglishTranslation(entry.key);
}
entry.setEnglish(englishTranslation);
String dutchTranslation = FlexoLocalization.localizedForKeyAndLanguage(parent, entry.key, Language.DUTCH);
if (entry.key.equals(dutchTranslation)) {
dutchTranslation = automaticDutchTranslation(entry.key);
}
entry.setDutch(dutchTranslation);
String frenchTranslation = FlexoLocalization.localizedForKeyAndLanguage(parent, entry.key, Language.FRENCH);
entry.setFrench(frenchTranslation);
} else {
String englishTranslation = entry.key.toString();
englishTranslation = englishTranslation.replace("_", " ");
englishTranslation = englishTranslation.substring(0, 1).toUpperCase() + englishTranslation.substring(1);
entry.setEnglish(englishTranslation);
entry.setDutch(englishTranslation);
}
}
private String automaticEnglishTranslation(String key) {
String englishTranslation = key.toString();
englishTranslation = englishTranslation.replace("_", " ");
englishTranslation = englishTranslation.substring(0, 1).toUpperCase() + englishTranslation.substring(1);
return englishTranslation;
}
private String automaticDutchTranslation(String key) {
return automaticEnglishTranslation(key);
}
@Override
public boolean registerNewEntry(String key, Language language, String value) {
addEntryInDictionary(language, key, value, true);
if (automaticSaving) {
save();
}
return true;
}
@Override
public LocalizedDelegate getParent() {
return parent;
}
public void translateAll() {
for (Entry entry : getEntries()) {
searchTranslation(entry);
}
}
protected Hashtable<Language, Properties> getLocalizedDictionaries() {
return _localizedDictionaries;
}
public File getLocalizedDirectory() {
return _localizedDirectory;
}
public String getParentDelegateDescription() {
if (getParent() == null) {
return "none";
} else {
return getParent().toString();
}
}
@Override
public String toString() {
return "Localization stored in " + getLocalizedDirectory().getAbsolutePath();
}
}