/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program 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 2 of the License, or (at your option) any later
* version.
*
* This program 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
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.awt;
import static org.andork.awt.CheckEDT.checkEDT;
import java.awt.Dialog;
import java.awt.Frame;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
import org.andork.bind.Binder;
import org.andork.util.Java7.Objects;
public class I18n {
private static class ActionNameUpdater implements I18nUpdater<Action> {
String key;
protected ActionNameUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, Action localizedObject) {
localizedObject.putValue(Action.NAME, localizer.getString(key));
}
}
private static class ButtonI18nUpdater implements I18nUpdater<AbstractButton> {
String key;
protected ButtonI18nUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, AbstractButton localizedObject) {
localizedObject.setText(localizer.getString(key));
}
}
private static class DialogTitleI18nUpdater implements I18nUpdater<Dialog> {
String key;
protected DialogTitleI18nUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, Dialog localizedObject) {
localizedObject.setTitle(localizer.getString(key));
}
}
private static class FrameTitleI18nUpdater implements I18nUpdater<Frame> {
String key;
protected FrameTitleI18nUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, Frame localizedObject) {
localizedObject.setTitle(localizer.getString(key));
}
}
public static interface I18nUpdater<T> {
public void updateI18n(Localizer localizer, T localizedObject);
}
private static class LabelI18nFormattedUpdater implements I18nUpdater<JLabel> {
String key;
Object[] args;
protected LabelI18nFormattedUpdater(String key, Object... args) {
this.key = key;
this.args = args;
}
@Override
public void updateI18n(Localizer localizer, JLabel localizedObject) {
localizedObject.setText(localizer.getFormattedString(key, args));
}
}
private static class LabelI18nUpdater implements I18nUpdater<JLabel> {
String key;
protected LabelI18nUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, JLabel localizedObject) {
localizedObject.setText(localizer.getString(key));
}
}
public static class LocalizedFormattedStringBinder extends Binder<String> implements I18nUpdater<String> {
Localizer localizer;
String key;
Binder<?>[] argBinders;
Object[] args;
String value;
public LocalizedFormattedStringBinder(Localizer localizer, String key, Binder<?>... argBinders) {
this.localizer = localizer;
this.key = key;
this.argBinders = argBinders;
localizer.register(key, this);
}
@Override
public String get() {
return value;
}
@Override
public void set(String newValue) {
throw new UnsupportedOperationException();
}
@Override
public void update(boolean force) {
boolean updateNeeded = force;
for (int i = 0; i < argBinders.length; i++) {
Object paramValue = argBinders[i].get();
if (!Objects.equals(args[i], paramValue)) {
args[i] = value;
updateNeeded = true;
}
}
if (updateNeeded) {
value = localizer.getFormattedString(key, args);
updateDownstream(force);
}
}
@Override
public void updateI18n(Localizer localizer, String localizedObject) {
update(true);
}
}
public static class LocalizedStringBinder extends Binder<String> implements I18nUpdater<String> {
Localizer localizer;
String key;
String value;
public LocalizedStringBinder(Localizer localizer, String key) {
this.localizer = localizer;
this.key = key;
localizer.register(key, this);
}
@Override
public String get() {
return value;
}
@Override
public void set(String newValue) {
throw new UnsupportedOperationException();
}
@Override
public void update(boolean force) {
String newText = localizer.getString(key);
if (!Objects.equals(newText, value) || force) {
value = newText;
updateDownstream(force);
}
}
@Override
public void updateI18n(Localizer localizer, String localizedObject) {
update(true);
}
}
public class Localizer {
public final String name;
private ResourceBundle bundle;
private final WeakHashMap<Object, I18nUpdater<?>> updaters = new WeakHashMap<Object, I18nUpdater<?>>();
private final Set<String> missingKeys = new HashSet<String>();
private Localizer(String name) {
super();
this.name = name;
updateBundle();
}
public Binder<String> bindFormattedString(String key, Binder<?>... argBinders) {
return new LocalizedFormattedStringBinder(this, key, argBinders);
}
public Binder<String> bindString(String key) {
return new LocalizedStringBinder(this, key);
}
public String getFormattedString(String key, Object... args) {
try {
return MessageFormat.format(getString(key), args);
} catch (Exception ex) {
if (missingKeys.add(key)) {
logger.log(Level.WARNING, "Missing I18n key: \"" + key + '"', ex);
}
return key;
}
}
public String getString(String key) {
try {
return bundle == null ? key : bundle.getString(key);
} catch (Exception ex) {
if (missingKeys.add(key)) {
logger.log(Level.WARNING, "Missing I18n key: \"" + key + '"', ex);
}
return key;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void localeChanged() {
updateBundle();
for (Map.Entry<Object, I18nUpdater<?>> entry : updaters.entrySet()) {
I18nUpdater updater = entry.getValue();
updater.updateI18n(this, entry.getKey());
}
}
public <T> void register(T localizedObject, I18nUpdater<T> updater) {
checkEDT();
updaters.put(localizedObject, updater);
updater.updateI18n(this, localizedObject);
}
public void setFormattedText(JLabel label, String key, Object... args) {
checkEDT();
register(label, new LabelI18nFormattedUpdater(key, args));
}
public void setName(Action action, String key) {
checkEDT();
register(action, new ActionNameUpdater(key));
}
public void setText(AbstractButton button, String key) {
checkEDT();
register(button, new ButtonI18nUpdater(key));
}
public void setText(JLabel label, String key) {
checkEDT();
register(label, new LabelI18nUpdater(key));
}
public void setTitle(Dialog dialog, String key) {
checkEDT();
register(dialog, new DialogTitleI18nUpdater(key));
}
public void setTitle(Frame frame, String key) {
checkEDT();
register(frame, new FrameTitleI18nUpdater(key));
}
public void setToolTipText(JComponent component, String key) {
checkEDT();
register(component, new ToolTipTextI18nUpdater(key));
}
public void unregister(Object localizedObject) {
checkEDT();
updaters.remove(localizedObject);
}
private void updateBundle() {
if (disableBundleLoading) {
return;
}
try {
bundle = ResourceBundle.getBundle(name, locale);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class ToolTipTextI18nUpdater implements I18nUpdater<JComponent> {
String key;
protected ToolTipTextI18nUpdater(String key) {
super();
this.key = key;
}
@Override
public void updateI18n(Localizer localizer, JComponent localizedObject) {
localizedObject.setToolTipText(localizer.getString(key));
}
}
private static final Logger logger = Logger.getLogger(I18n.class.getName());
private Locale locale;
private final Map<String, Localizer> localizers = new HashMap<String, Localizer>();
private boolean disableBundleLoading = System.getProperties().containsKey("disableBundleLoading");
public I18n() {
locale = Locale.getDefault();
}
public Localizer forClass(Class<?> cls) {
return forName(cls.getName());
}
public Localizer forName(String name) {
checkEDT();
Localizer result = localizers.get(name);
if (result == null) {
result = new Localizer(name);
localizers.put(name, result);
}
return result;
}
public Locale getLocale() {
checkEDT();
return locale;
}
public boolean isDisableBundleLoading() {
return disableBundleLoading;
}
public void setDisableBundleLoading(boolean disableBundleLoading) {
this.disableBundleLoading = disableBundleLoading;
}
public void setLocale(Locale locale) {
checkEDT();
if (!this.locale.equals(locale)) {
this.locale = locale;
for (Localizer localizer : localizers.values()) {
localizer.localeChanged();
}
}
}
}