/*
* (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.awt.Component;
import java.awt.Frame;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.border.TitledBorder;
import javax.swing.table.TableColumn;
import org.openflexo.xmlcode.InvalidObjectSpecificationException;
import org.openflexo.xmlcode.KeyValueDecoder;
/**
* This utility class implement localization support <br>
*
* A hierarchical structure of localized delegates are internally used. If no localization is found for a given delegate, then the parent
* delegate will be recursively invoked. See {@link LocalizedDelegate}.<br>
*
* There are two main ways to use this class:
* <ul>
* <li>Either use methods with a supplied localized delegate</li>
* <li>Or use methods without specifying a localized delegate</li>
* </ul>
* In second case, this class MUST have been initialized with a given localizer (localized delegate).
*
* @author sylvain
*/
public class FlexoLocalization {
private static final Logger logger = Logger.getLogger(FlexoLocalization.class.getPackage().getName());
private static Language _currentLanguage;
private static List<WeakReference<LocalizationListener>> localizationListeners = new Vector<WeakReference<LocalizationListener>>();
private static final WeakHashMap<Component, String> _storedLocalizedForComponents = new WeakHashMap<Component, String>();
private static final WeakHashMap<JComponent, String> _storedLocalizedForComponentTooltips = new WeakHashMap<JComponent, String>();
private static final WeakHashMap<TitledBorder, String> _storedLocalizedForBorders = new WeakHashMap<TitledBorder, String>();
private static final WeakHashMap<TableColumn, String> _storedLocalizedForTableColumn = new WeakHashMap<TableColumn, String>();
private static final WeakHashMap<Component, String> _storedAdditionalStrings = new WeakHashMap<Component, String>();
private static LocalizedDelegate mainLocalizer;
private static boolean uninitalizedLocalizationWarningDone = false;
/**
* Initialize localization given a supplied localization delegate
*
* @param delegate
*/
public static void initWith(LocalizedDelegate delegate) {
if (!FlexoLocalization.isInitialized()) {
mainLocalizer = delegate;
} else {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Locales have already been initialized. Unless you know what you are doing, this should not happen is therefore prevented.");
}
}
}
/**
* Return a flag indicating if localization has been initialized
*
* @return
*/
public static boolean isInitialized() {
return mainLocalizer != null;
}
/**
* Initialize localization given a supplied directory<br>
* This directory will be used as the location of a main localizer that will be instanciated here, as an instance of
* LocalizedDelegateImpl
*
* @param localizedDirectory
*/
public static void initWith(File localizedDirectory) {
mainLocalizer = new LocalizedDelegateImpl(localizedDirectory, null, false);
}
/**
* This is general and main method to use localized in Flexo.<br>
* Applicable language is chosen from the one defined in Preferences.<br>
* Use english names for keys, such as 'some_english_words'
*
* @param key
* @return localized String
*/
public static String localizedForKey(String key) {
if (mainLocalizer == null) {
if (!uninitalizedLocalizationWarningDone) {
logger.warning("FlexoLocalization not initialized, returning key as localized key=" + key);
uninitalizedLocalizationWarningDone = true;
}
return key;
}
return localizedForKey(mainLocalizer, key);
}
public static String localizedForKey(LocalizedDelegate delegate, String key) {
return localizedForKeyAndLanguage(delegate, key, getCurrentLanguage());
}
public static String localizedForKeyAndLanguage(String key, Language language) {
if (mainLocalizer == null) {
if (!uninitalizedLocalizationWarningDone) {
logger.warning("FlexoLocalization not initialized, returning key as localized key=" + key);
uninitalizedLocalizationWarningDone = true;
}
return key;
}
return localizedForKeyAndLanguage(mainLocalizer, key, language);
}
public static String localizedForKeyAndLanguage(LocalizedDelegate delegate, String key, Language language) {
return localizedForKeyAndLanguage(delegate, key, language, true);
}
public static String localizedForKeyAndLanguage(LocalizedDelegate delegate, String key, Language language,
boolean createsNewEntriesIfNonExistant) {
if (key == null) {
return null;
}
if (delegate == null) {
return key;
}
String returned = delegate.getLocalizedForKeyAndLanguage(key, language);
if (returned == null) {
// Not found
if (createsNewEntriesIfNonExistant && delegate.handleNewEntry(key, language)) {
// We have to register this new entries
if (delegate.getParent() != null) {
// A parent exists, we will use its localized values in current localizer
for (Language l : Language.availableValues()) {
String value = localizedForKeyAndLanguage(delegate.getParent(), key, l, false);
delegate.registerNewEntry(key, l, value);
}
return localizedForKeyAndLanguage(delegate.getParent(), key, language, false);
} else {
// No parent exists, we will use keys
for (Language l : Language.availableValues()) {
delegate.registerNewEntry(key, l, key);
}
return key;
}
} else {
if (delegate.getParent() != null) {
return localizedForKeyAndLanguage(delegate.getParent(), key, language, false);
} else {
// logger.warning("Not found and not handling new entries for localized for key " + key + " delegate=" + delegate);
return key;
}
}
} else {
return returned;
}
}
public static String localizedForKeyWithParams(String key, Object... object) {
String base = localizedForKey(key);
return replaceAllParamsInString(base, object);
}
public static String localizedForKeyWithParams(LocalizedDelegate delegate, String key, Object... object) {
String base = localizedForKey(delegate, key);
return replaceAllParamsInString(base, object);
}
/**
* Convenient methods localizing an array of keys
*
* @param keys
* @return
*/
public static String[] localizedForKey(String[] keys) {
String[] values = new String[keys.length];
for (int i = 0; i < keys.length; i++) {
values[i] = localizedForKey(keys[i]);
}
return values;
}
/**
* Sets localized related to specified key and language
*
* @param key
* , value, language
* @return localized String
*/
public static void setLocalizedForKeyAndLanguage(String key, String value, Language language) {
if (mainLocalizer == null) {
if (!uninitalizedLocalizationWarningDone) {
logger.warning("FlexoLocalization not initialized, returning key as localized key=" + key);
uninitalizedLocalizationWarningDone = true;
}
} else {
setLocalizedForKeyAndLanguage(mainLocalizer, key, value, language);
}
}
/**
* Sets localized related to specified key and language
*
* @param key
* , value, language
* @return localized String
*/
public static void setLocalizedForKeyAndLanguage(@Nonnull LocalizedDelegate delegate, String key, String value, Language language) {
if (delegate.handleNewEntry(key, language)) {
delegate.registerNewEntry(key, language, value);
}
}
/**
* Sets localized related to specified key
*
* @param key
* , value, language
* @return localized String
*/
public static void setLocalizedForKey(String key, String value) {
setLocalizedForKeyAndLanguage(key, value, getCurrentLanguage());
}
public static String localizedForKey(LocalizedDelegate delegate, String key, Component component) {
if (logger.isLoggable(Level.FINE)) {
logger.finest("localizedForKey called with " + key + " for " + component.getClass().getName());
}
_storedLocalizedForComponents.put(component, key);
return localizedForKey(delegate, key);
}
public static String localizedTooltipForKey(String key, JComponent component) {
return localizedTooltipForKey(mainLocalizer, key, component);
}
public static String localizedTooltipForKey(LocalizedDelegate delegate, String key, JComponent component) {
if (logger.isLoggable(Level.FINE)) {
logger.finest("localizedForKey called with " + key + " for " + component.getClass().getName());
}
_storedLocalizedForComponentTooltips.put(component, key);
return localizedForKey(delegate, key);
}
public static String localizedForKey(String key, Component component) {
return localizedForKey(mainLocalizer, key, component);
}
public static String localizedForKey(String key, TitledBorder border) {
if (logger.isLoggable(Level.FINE)) {
logger.finest("localizedForKey called with " + key + " for border " + border.getClass().getName());
}
_storedLocalizedForBorders.put(border, key);
return localizedForKey(key);
}
public static String localizedForKey(String key, TableColumn column) {
if (logger.isLoggable(Level.FINE)) {
logger.finest("localizedForKey called with " + key + " for border " + column.getClass().getName());
}
_storedLocalizedForTableColumn.put(column, key);
return localizedForKey(key);
}
public static String localizedForKey(String key, String additionalString, Component component) {
if (key == null) {
return null;
}
if (logger.isLoggable(Level.FINE)) {
logger.finest("localizedForKey called with " + key + " for " + component.getClass().getName());
}
_storedLocalizedForComponents.put(component, key);
_storedAdditionalStrings.put(component, additionalString);
return localizedForKey(key) + additionalString;
}
public static void updateGUILocalized() {
for (Map.Entry<Component, String> e : _storedLocalizedForComponents.entrySet()) {
Component component = e.getKey();
String string = e.getValue();
String text = localizedForKey(string);
String additionalString = _storedAdditionalStrings.get(component);
if (additionalString != null) {
text = text + additionalString;
}
if (component instanceof AbstractButton) {
((AbstractButton) component).setText(text);
}
if (component instanceof JLabel) {
((JLabel) component).setText(text);
}
component.setName(text);
if (component.getParent() instanceof JTabbedPane) {
if (((JTabbedPane) component.getParent()).indexOfComponent(component) > -1) {
((JTabbedPane) component.getParent()).setTitleAt(((JTabbedPane) component.getParent()).indexOfComponent(component),
text);
}
}
if (component.getParent() != null && component.getParent().getParent() instanceof JTabbedPane) {
if (((JTabbedPane) component.getParent().getParent()).indexOfComponent(component) > -1) {
((JTabbedPane) component.getParent().getParent()).setTitleAt(
((JTabbedPane) component.getParent().getParent()).indexOfComponent(component), text);
}
}
}
for (Map.Entry<JComponent, String> e : _storedLocalizedForComponentTooltips.entrySet()) {
JComponent component = e.getKey();
String string = e.getValue();
String text = localizedForKey(string);
component.setToolTipText(text);
}
for (Map.Entry<TitledBorder, String> e : _storedLocalizedForBorders.entrySet()) {
String string = e.getValue();
String text = localizedForKey(string);
e.getKey().setTitle(text);
}
for (Map.Entry<TableColumn, String> e : _storedLocalizedForTableColumn.entrySet()) {
String string = e.getValue();
String text = localizedForKey(string);
e.getKey().setHeaderValue(text);
}
for (Frame f : Frame.getFrames()) {
f.repaint();
}
}
/**
* Returns a vector containing all available languages as a Vector of Language objects
*
* @return Vector of Language objects
*/
public static List<Language> getAvailableLanguages() {
return Language.getAvailableLanguages();
}
public static void setCurrentLanguage(Language language) {
_currentLanguage = language;
Iterator<WeakReference<LocalizationListener>> i = localizationListeners.iterator();
while (i.hasNext()) {
LocalizationListener l = i.next().get();
if (l == null) {
i.remove();
} else {
l.languageChanged(language);
}
}
}
public static Language getCurrentLanguage() {
if (_currentLanguage == null) {
_currentLanguage = Language.ENGLISH;
}
return _currentLanguage;
}
public static LocalizedDelegate getMainLocalizer() {
return mainLocalizer;
}
public static void main(String[] args) {
System.out.println("Testing localizedForKeyWithParams(String,String...): "
+ localizedForKeyWithParams(
"Must display first param here ($0) and second one: ($1) and third one:($2) then repeat second one ($1)", "foo1",
"foo2", "foo3"));
}
/**
* @param returned
* @param object
*/
private static String replaceAllParamsInString(String aString, Object... object) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("replaceAllParamsInString() with " + aString + " and " + object);
}
// Pattern p = Pattern.compile("*\\($[a-zA-Z]\\)*");
// Pattern p = Pattern.compile("\\p{Punct}\\bJava(\\w*)\\p{Punct}");
// Pattern p = Pattern.compile("\\(\\bJava(\\w*)\\)");
// Pattern p = Pattern.compile("\\(\\$(\\w*)\\)");
Pattern p = Pattern.compile("\\(\\$([_0-9a-zA-Z[\\.]]+)\\)");
Matcher m = p.matcher(aString);
StringBuffer returned = new StringBuffer();
while (m.find()) {
int nextIndex = m.start();
String foundPattern = m.group();
if (logger.isLoggable(Level.FINE)) {
logger.finest("Found '" + foundPattern + "' at position " + nextIndex);
}
String suffix = m.group(1);
if (logger.isLoggable(Level.FINE)) {
logger.finest("Suffix is " + suffix);
}
try {
int index = Integer.parseInt(suffix);
if (object.length > index) {
if (object[index] != null) {
m.appendReplacement(returned, object[index].toString());
} else {
m.appendReplacement(returned, "");
}
} else {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Argument index " + index + " is greater than number of arguments");
}
}
} catch (NumberFormatException e) {
m.appendReplacement(returned, valueForKeyAndObject(suffix, object[0]));
}
}
m.appendTail(returned);
if (logger.isLoggable(Level.FINE)) {
logger.finer("Returning " + returned);
}
return returned.toString();
}
private static String valueForKeyAndObject(String key, Object object) {
try {
// Object objectForKey = BindingEvaluator.evaluateBinding(key, object);
Object objectForKey = KeyValueDecoder.valueForKey(object, key);
if (objectForKey != null) {
return objectForKey.toString();
} else {
return "";
}
} catch (InvalidObjectSpecificationException e) {
logger.warning(e.getMessage());
return key;
/*
* }catch (InvalidKeyValuePropertyException e) { logger.warning(e.getMessage()); return key;
*/
}
}
public static void clearStoredLocalizedForComponents() {
_storedLocalizedForComponents.clear();
_storedLocalizedForBorders.clear();
_storedAdditionalStrings.clear();
_storedLocalizedForTableColumn.clear();
localizationListeners.clear();
}
public static void addToLocalizationListeners(LocalizationListener l) {
localizationListeners.add(new WeakReference<LocalizationListener>(l));
}
public static void removeFromLocalizationListeners(LocalizationListener l) {
Iterator<WeakReference<LocalizationListener>> i = localizationListeners.iterator();
while (i.hasNext()) {
LocalizationListener l1 = i.next().get();
if (l1 == null || l1 == l) {
i.remove();
}
}
}
}