Fix resource bundle bug, is now 1.6+ only
[nikiroo-utils.git] / src / be / nikiroo / utils / resources / Bundle.java
CommitLineData
ec1f3444
NR
1package be.nikiroo.utils.resources;
2
3import java.io.BufferedWriter;
4import java.io.File;
5import java.io.FileInputStream;
6import java.io.FileOutputStream;
7import java.io.IOException;
8import java.io.InputStreamReader;
9import java.io.OutputStreamWriter;
10import java.io.Reader;
ec1f3444
NR
11import java.io.Writer;
12import java.lang.reflect.Field;
13import java.util.ArrayList;
14import java.util.List;
15import java.util.Locale;
16import java.util.MissingResourceException;
17import java.util.PropertyResourceBundle;
18import java.util.ResourceBundle;
19
ec1f3444
NR
20/**
21 * This class encapsulate a {@link ResourceBundle} in UTF-8. It only allows to
22 * retrieve values associated to an enumeration, and allows some additional
23 * methods.
24 *
25 * @author niki
26 *
27 * @param <E>
28 * the enum to use to get values out of this class
29 */
30public class Bundle<E extends Enum<E>> {
31 protected Class<E> type;
32 protected Enum<?> name;
33 private ResourceBundle map;
34
35 /**
36 * Create a new {@link Bundles} of the given name.
37 *
38 * @param type
39 * a runtime instance of the class of E
40 *
41 * @param name
42 * the name of the {@link Bundles}
43 */
44 protected Bundle(Class<E> type, Enum<?> name) {
45 this.type = type;
46 this.name = name;
47 setBundle(name, Locale.getDefault());
48 }
49
50 /**
51 * Return the value associated to the given id as a {@link String}.
52 *
53 * @param mame
54 * the id of the value to get
55 *
56 * @return the associated value, or NULL if not found (not present in the
57 * resource file)
58 */
59 public String getString(E id) {
60 return getStringX(id, "");
61 }
62
63 /**
64 * Return the value associated to the given id as a {@link String} suffixed
65 * with the runtime value "_suffix" (that is, "_" and suffix).
66 *
67 * @param mame
68 * the id of the value to get
69 * @param suffix
70 * the runtime suffix
71 *
72 * @return the associated value, or NULL if not found (not present in the
73 * resource file)
74 */
75 public String getStringX(E id, String suffix) {
76 String key = id.name()
77 + ((suffix == null || suffix.isEmpty()) ? "" : "_"
78 + suffix.toUpperCase());
79
80 if (containsKey(key)) {
81 return getString(key).trim();
82 }
83
84 return null;
85 }
86
87 /**
88 * Return the value associated to the given id as a {@link Boolean}.
89 *
90 * @param mame
91 * the id of the value to get
92 *
93 * @return the associated value
94 */
95 public Boolean getBoolean(E id) {
96 String str = getString(id);
97 if (str != null && str.length() > 0) {
98 if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("on")
99 || str.equalsIgnoreCase("yes"))
100 return true;
101 if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("off")
102 || str.equalsIgnoreCase("no"))
103 return false;
104
105 }
106
107 return null;
108 }
109
110 /**
111 * Return the value associated to the given id as a {@link boolean}.
112 *
113 * @param mame
114 * the id of the value to get
115 * @param def
116 * the default value when it is not present in the config file or
117 * if it is not a boolean value
118 *
119 * @return the associated value
120 */
121 public boolean getBoolean(E id, boolean def) {
122 Boolean b = getBoolean(id);
123 if (b != null)
124 return b;
125
126 return def;
127 }
128
129 /**
130 * Return the value associated to the given id as an {@link Integer}.
131 *
132 * @param mame
133 * the id of the value to get
134 *
135 * @return the associated value
136 */
137 public Integer getInteger(E id) {
138 try {
139 return Integer.parseInt(getString(id));
140 } catch (Exception e) {
141 }
142
143 return null;
144 }
145
146 /**
147 * Return the value associated to the given id as a {@link int}.
148 *
149 * @param mame
150 * the id of the value to get
151 * @param def
152 * the default value when it is not present in the config file or
153 * if it is not a int value
154 *
155 * @return the associated value
156 */
157 public int getInteger(E id, int def) {
158 Integer i = getInteger(id);
159 if (i != null)
160 return i;
161
162 return def;
163 }
164
165 /**
166 * Return the value associated to the given id as a {@link Character}.
167 *
168 * @param mame
169 * the id of the value to get
170 *
171 * @return the associated value
172 */
173 public char getChar(E id) {
174 String s = getString(id).trim();
175 if (s.length() > 0) {
176 return s.charAt(0);
177 }
178
179 return ' ';
180 }
181
182 /**
183 * Create/update the .properties file. Will use the most likely candidate as
184 * base if the file does not already exists and this resource is
185 * translatable (for instance, "en_US" will use "en" as a base if the
186 * resource is a translation file).
187 *
188 * @param path
189 * the path where the .properties files are
190 *
191 * @throws IOException
192 * in case of IO errors
193 */
194 public void updateFile(String path) throws IOException {
195 File file = getUpdateFile(path);
196
197 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
198 new FileOutputStream(file), "UTF-8"));
199
200 writeHeader(writer);
201 writer.write("\n");
202 writer.write("\n");
203
204 for (Field field : type.getDeclaredFields()) {
205 Meta meta = field.getAnnotation(Meta.class);
206 if (meta != null) {
207 E id = E.valueOf(type, field.getName());
208 String info = getMetaInfo(meta);
209
210 if (info != null) {
211 writer.write(info);
212 writer.write("\n");
213 }
214
215 writeValue(writer, id);
216 }
217 }
218
219 writer.close();
220 }
221
222 /**
223 * Check if the internal map contains the given key.
224 *
225 * @param key
226 * the key to check for
227 *
228 * @return true if it does
229 */
230 protected boolean containsKey(String key) {
231 try {
232 map.getObject(key);
233 return true;
234 } catch (MissingResourceException e) {
235 return false;
236 }
237 }
238
239 /**
2cce3dcb
NR
240 * Get the value for the given key if it exists in the internal map, or NULL
241 * if not.
ec1f3444
NR
242 *
243 * @param key
244 * the key to check for
245 *
2cce3dcb 246 * @return the value, or NULL
ec1f3444
NR
247 */
248 protected String getString(String key) {
249 if (containsKey(key)) {
2cce3dcb 250 return map.getString(key);
ec1f3444
NR
251 }
252
253 return null;
254 }
255
256 /**
257 * Return formated, display-able information from the {@link Meta} field
258 * given. Each line will always starts with a "#" character.
259 *
260 * @param meta
261 * the {@link Meta} field
262 *
263 * @return the information to display or NULL if none
264 */
265 protected String getMetaInfo(Meta meta) {
266 String what = meta.what();
267 String where = meta.where();
268 String format = meta.format();
269 String info = meta.info();
270
271 int opt = what.length() + where.length() + format.length();
272 if (opt + info.length() == 0)
273 return null;
274
275 StringBuilder builder = new StringBuilder();
276 builder.append("# ");
277
278 if (opt > 0) {
279 builder.append("(");
280 if (what.length() > 0) {
281 builder.append("WHAT: " + what);
282 if (where.length() + format.length() > 0)
283 builder.append(", ");
284 }
285
286 if (where.length() > 0) {
287 builder.append("WHERE: " + where);
288 if (format.length() > 0)
289 builder.append(", ");
290 }
291
292 if (format.length() > 0) {
293 builder.append("FORMAT: " + format);
294 }
295
296 builder.append(")");
297 if (info.length() > 0) {
298 builder.append("\n# ");
299 }
300 }
301
302 builder.append(info);
303
304 return builder.toString();
305 }
306
307 /**
308 * The display name used in the <tt>.properties file</tt>.
309 *
310 * @return the name
311 */
312 protected String getBundleDisplayName() {
313 return name.toString();
314 }
315
316 /**
317 * Write the header found in the configuration <tt>.properties</tt> file of
318 * this {@link Bundles}.
319 *
320 * @param writer
321 * the {@link Writer} to write the header in
322 *
323 * @throws IOException
324 * in case of IO error
325 */
326 protected void writeHeader(Writer writer) throws IOException {
327 writer.write("# " + getBundleDisplayName() + "\n");
328 writer.write("#\n");
329 }
330
331 /**
332 * Write the given id to the config file, i.e., "MY_ID = my_curent_value"
333 * followed by a new line
334 *
335 * @param writer
336 * the {@link Writer} to write into
337 * @param id
338 * the id to write
339 *
340 * @throws IOException
341 * in case of IO error
342 */
343 protected void writeValue(Writer writer, E id) throws IOException {
344 writeValue(writer, id.name(), getString(id));
345 }
346
347 /**
348 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
349 * followed by a new line
350 *
351 * @param writer
352 * the {@link Writer} to write into
353 * @param id
354 * the id to write
355 * @param value
356 * the id's value
357 *
358 * @throws IOException
359 * in case of IO error
360 */
361 protected void writeValue(Writer writer, String id, String value)
362 throws IOException {
363 writer.write(id);
364 writer.write(" = ");
365
366 if (value == null) {
367 value = "";
368 }
369
370 String[] lines = value.replaceAll("\\\t", "\\\\\\t").split("\n");
371 for (int i = 0; i < lines.length; i++) {
372 writer.write(lines[i]);
373 if (i < lines.length - 1) {
374 writer.write("\\n\\");
375 }
376 writer.write("\n");
377 }
378 }
379
380 /**
381 * Return the source file for this {@link Bundles} from the given path.
382 *
383 * @param path
384 * the path where the .properties files are
385 *
386 * @return the source {@link File}
387 *
388 * @throws IOException
389 * in case of IO errors
390 */
391 protected File getUpdateFile(String path) {
392 return new File(path, name.name() + ".properties");
393 }
394
395 /**
396 * Change the currently used bundle.
397 *
398 * @param name
399 * the name of the bundle to load
400 * @param locale
401 * the {@link Locale} to use
402 */
403 protected void setBundle(Enum<?> name, Locale locale) {
404 map = null;
405 String dir = Bundles.getDirectory();
406
407 if (dir != null) {
408 try {
409 File file = getPropertyFile(dir, name.name(), locale);
410 if (file != null) {
411 Reader reader = new InputStreamReader(new FileInputStream(
412 file), "UTF8");
413 map = new PropertyResourceBundle(reader);
414 }
415 } catch (IOException e) {
416 e.printStackTrace();
417 }
418 }
419
420 if (map == null) {
421 map = ResourceBundle.getBundle(type.getPackage().getName() + "."
2cce3dcb 422 + name.name(), locale, new FixedResourceBundleControl());
ec1f3444
NR
423 }
424 }
425
426 /**
427 * Return the resource file that is closer to the {@link Locale}.
428 *
429 * @param dir
430 * the dirctory to look into
431 * @param name
432 * the file basename (without <tt>.properties</tt>)
433 * @param locale
434 * the {@link Locale}
435 *
436 * @return the closest match or NULL if none
437 */
438 private File getPropertyFile(String dir, String name, Locale locale) {
439 List<String> locales = new ArrayList<String>();
440 if (locale != null) {
441 String country = locale.getCountry() == null ? "" : locale
442 .getCountry();
443 String language = locale.getLanguage() == null ? "" : locale
444 .getLanguage();
445 if (!language.isEmpty() && !country.isEmpty()) {
446 locales.add("_" + language + "-" + country);
447 }
448 if (!language.isEmpty()) {
449 locales.add("_" + language);
450 }
451 }
452
453 locales.add("");
454
455 File file = null;
456 for (String loc : locales) {
457 file = new File(dir, name + loc + ".properties");
458 if (file.exists()) {
459 break;
460 } else {
461 file = null;
462 }
463 }
464
465 return file;
466 }
467}