001package fr.aumgn.bukkitutils.localization;
002
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.io.InputStreamReader;
008import java.io.Reader;
009import java.net.URL;
010import java.net.URLConnection;
011import java.text.MessageFormat;
012import java.util.ArrayDeque;
013import java.util.Deque;
014import java.util.HashMap;
015import java.util.Iterator;
016import java.util.LinkedHashSet;
017import java.util.Locale;
018import java.util.Map;
019import java.util.Set;
020
021import org.bukkit.plugin.java.JavaPlugin;
022
023import com.google.common.base.Charsets;
024
025import fr.aumgn.bukkitutils.localization.loaders.JsonMessagesLoader;
026import fr.aumgn.bukkitutils.localization.loaders.MessagesLoader;
027import fr.aumgn.bukkitutils.localization.loaders.PropertiesMessagesLoader;
028import fr.aumgn.bukkitutils.localization.loaders.YamlMessagesLoader;
029
030/**
031 * Class which handles loading of localization
032 * resources for the given plugin and locale.
033 *
034 * This class looks for resources in the plugin's jarfile
035 * (in the plugin's package) and in the plugin's data folder.
036 *
037 * Resources are defined by a name to which is appended
038 * a suffix corresponding to the locale.
039 *
040 * For example if you're looking for a resource with the
041 * name "messages" for the plugin MyPlugin whose main class
042 * is "fr.aumgn.myplugin.MyPlugin" and the locale "fr_FR",
043 * the class will go through all this steps :
044 * <ul>
045 *   <li>
046 *     Looks for the resource
047 *     fr/aumgn/myplugin/messages_en.{json,yml,properties}
048 *     in the jarfile
049 *   </li>
050 *   <li>
051 *     Looks for the resource
052 *     [server]/plugins/MyPlugin/messages_en.{json,yml,properties}
053 *   </li>
054 *   <li>
055 *     Looks for the resource
056 *     fr/aumgn/myplugin/messages_en_US.{json,yml,properties}
057 *     in the jarfile
058 *   </li>
059 *   <li>
060 *     Looks for the resource
061 *     [server]/plugins/MyPlugin/messages_en_US.{json,yml,properties}
062 *   </li>
063 *   <li>
064 *     Looks for the resource
065 *     fr/aumgn/myplugin/messages_fr.{json,yml,properties}
066 *     in the jarfile
067 *   </li>
068 *   <li>
069 *     Looks for the resource
070 *     [server]/plugins/MyPlugin/messages_fr.{json,yml,properties}
071 *   </li>
072 *   <li>
073 *     Looks for the resource
074 *     fr/aumgn/myplugin/messages_fr_FR.{json,yml,properties}
075 *     in the jarfile
076 *   </li>
077 *   <li>
078 *     Looks for the resource
079 *     [server]/plugins/MyPlugin/messages_fr_FR.{json,yml,properties}
080 *   </li>
081 * </ul>
082 *
083 * Each values find in a loaded resources has priority
084 * over values find in previous resources.
085 */
086public class Localization {
087
088    public static final Locale DEFAULT_LOCALE = Locale.US;
089
090    private static final Deque<MessagesLoader> loaders =
091            new ArrayDeque<MessagesLoader>();
092
093    /**
094     * Registers a MessagesLoader.
095     */
096    public static void register(MessagesLoader loader) {
097        loaders.push(loader);
098    }
099
100    protected static Iterable<MessagesLoader> loaders() {
101        return new Iterable<MessagesLoader>() {
102            @Override
103            public Iterator<MessagesLoader> iterator() {
104                return loaders.descendingIterator();
105            }
106        };
107    }
108
109    static {
110        register(new PropertiesMessagesLoader());
111        register(new YamlMessagesLoader());
112        register(new JsonMessagesLoader());
113    }
114
115    public static Locale[] localesLookupFor(Locale targetLocale,
116            Locale fallbackLocale) {
117        Set<Locale> localesSet = new LinkedHashSet<Locale>(4);
118        localesSet.add(targetLocale);
119        localesSet.add(new Locale(targetLocale.getLanguage()));
120        localesSet.add(fallbackLocale);
121        localesSet.add(new Locale(fallbackLocale.getLanguage()));
122        Locale[] locales = new Locale[localesSet.size()];
123        int i = locales.length - 1;
124        for (Locale locale : localesSet) {
125            locales[i] = locale;
126            i--;
127        }
128        return locales;
129    }
130
131    protected final JavaPlugin plugin;
132    private final File dir;
133    protected final Locale[] locales;
134
135    public Localization(JavaPlugin plugin) {
136        this(plugin, DEFAULT_LOCALE);
137    }
138
139    public Localization(JavaPlugin plugin, Locale targetLocale) {
140        this(plugin, plugin.getDataFolder(), targetLocale);
141    }
142
143    public Localization(JavaPlugin plugin, Locale targetLocale,
144            Locale fallbackLocale) {
145        this(plugin,  plugin.getDataFolder(), targetLocale, fallbackLocale);
146    }
147
148    public Localization(JavaPlugin plugin, File dir, Locale targetLocale) {
149        this(plugin, dir, targetLocale, DEFAULT_LOCALE);
150    }
151
152    public Localization(JavaPlugin plugin, File dir, Locale targetLocale,
153            Locale fallbackLocale) {
154        this.plugin = plugin;
155        this.dir = dir;
156        this.locales = localesLookupFor(targetLocale, fallbackLocale);
157    }
158
159    /**
160     * Loads the resources with the given name.
161     */
162    public PluginMessages get(String name) {
163        return new PluginMessages(loadMap(name));
164    }
165
166    protected Map<String, MessageFormat> loadMap(String name) {
167        Map<String, MessageFormat> map = new HashMap<String, MessageFormat>();
168        for (Locale locale : locales) {
169            load(map, locale, name + "_" + locale.toString());
170        }
171        return map;
172    }
173
174    protected void load(Map<String, MessageFormat> map, Locale locale,
175            String name) {
176        loadBundled(map, locale, name);
177        loadUser(map, locale, name);
178    }
179
180    protected void loadStream(Map<String, MessageFormat> map, Locale locale,
181            MessagesLoader loader, InputStream iStream) {
182        Reader reader =
183                new InputStreamReader(iStream, Charsets.UTF_8);
184        map.putAll(loader.load(locale, reader));
185        try {
186            reader.close();
187        } catch (IOException exc) {
188        }
189    }
190
191    private void loadBundled(Map<String, MessageFormat> map,  Locale locale,
192            String baseName) {
193        if (plugin == null) {
194            return;
195        }
196
197        for (MessagesLoader loader : loaders()) {
198            for (String extension : loader.getExtensions()) {
199                String name = baseName + "." + extension;
200
201                InputStream iStream = null;
202                URL res = plugin.getClass().getResource(name);
203                if (res != null) {
204                    try {
205                        URLConnection connection = res.openConnection();
206                        connection.setUseCaches(false);
207                        iStream = connection.getInputStream();
208                    } catch (IOException _) {
209                    }
210                }
211
212                if (iStream != null) {
213                    loadStream(map, locale, loader, iStream);
214                    return;
215                }
216            }
217        }
218    }
219
220    private void loadUser(Map<String, MessageFormat> map, Locale locale,
221            String baseName) {
222        if (dir == null) {
223            return;
224        }
225
226        for (MessagesLoader loader : loaders()) {
227            for (String extension : loader.getExtensions()) {
228                String name = baseName + "." + extension;
229
230                File file = new File(dir, name);
231                if (file.exists()) {
232                    try {
233                        InputStream iStream = new FileInputStream(file);
234                        loadStream(map, locale, loader, iStream);
235                    } catch (IOException exc) {
236                    }
237                }
238            }
239        }
240    }
241}