Improve cache + jdoc, traces
[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;
62c9ec78 14import java.util.HashMap;
ec1f3444
NR
15import java.util.List;
16import java.util.Locale;
62c9ec78 17import java.util.Map;
ec1f3444
NR
18import java.util.MissingResourceException;
19import java.util.PropertyResourceBundle;
20import java.util.ResourceBundle;
21
b15a4985
NR
22import be.nikiroo.utils.resources.Meta.Format;
23
ec1f3444 24/**
62c9ec78 25 * This class encapsulate a {@link ResourceBundle} in UTF-8. It allows to
ec1f3444
NR
26 * retrieve values associated to an enumeration, and allows some additional
27 * methods.
62c9ec78
NR
28 * <p>
29 * It also sports a writable change map, and you can save back the
30 * {@link Bundle} to file with {@link Bundle#updateFile(String)}.
ec1f3444 31 *
ec1f3444
NR
32 * @param <E>
33 * the enum to use to get values out of this class
db31c358
NR
34 *
35 * @author niki
ec1f3444 36 */
db31c358 37
ec1f3444 38public class Bundle<E extends Enum<E>> {
db31c358 39 /** The type of E. */
ec1f3444 40 protected Class<E> type;
db31c358
NR
41 /**
42 * The {@link Enum} associated to this {@link Bundle} (all the keys used in
43 * this {@link Bundle} will be of this type).
44 */
45 protected Enum<?> keyType;
46
47 private TransBundle<E> descriptionBundle;
48
49 /** R/O map */
50 private Map<String, String> map;
51 /** R/W map */
52 private Map<String, String> changeMap;
ec1f3444
NR
53
54 /**
55 * Create a new {@link Bundles} of the given name.
56 *
57 * @param type
58 * a runtime instance of the class of E
ec1f3444
NR
59 * @param name
60 * the name of the {@link Bundles}
db31c358
NR
61 * @param descriptionBundle
62 * the description {@link TransBundle}, that is, a
63 * {@link TransBundle} dedicated to the description of the values
64 * of the given {@link Bundle} (can be NULL)
ec1f3444 65 */
db31c358
NR
66 protected Bundle(Class<E> type, Enum<?> name,
67 TransBundle<E> descriptionBundle) {
ec1f3444 68 this.type = type;
db31c358
NR
69 this.keyType = name;
70 this.descriptionBundle = descriptionBundle;
71
487926f7 72 this.map = new HashMap<String, String>();
62c9ec78 73 this.changeMap = new HashMap<String, String>();
e9ca6bb8 74 setBundle(name, Locale.getDefault(), false);
ec1f3444
NR
75 }
76
13bfeea6
NR
77 /**
78 * Check if the setting is set into this {@link Bundle}.
79 *
80 * @param id
81 * the id of the setting to check
82 * @param includeDefaultValue
83 * TRUE to only return false when the setting is not set AND
84 * there is no default value
85 *
86 * @return TRUE if the setting is set
87 */
b15a4985
NR
88 public boolean isSet(E id, boolean includeDefaultValue) {
89 return isSet(id.name(), includeDefaultValue);
90 }
91
92 /**
93 * Check if the setting is set into this {@link Bundle}.
94 *
95 * @param id
96 * the id of the setting to check
97 * @param includeDefaultValue
98 * TRUE to only return false when the setting is not set AND
99 * there is no default value
100 *
101 * @return TRUE if the setting is set
102 */
103 protected boolean isSet(String name, boolean includeDefaultValue) {
104 if (getString(name, null) == null) {
105 if (!includeDefaultValue || getString(name, "") == null) {
13bfeea6
NR
106 return false;
107 }
108 }
109
110 return true;
111 }
112
ec1f3444
NR
113 /**
114 * Return the value associated to the given id as a {@link String}.
115 *
db31c358 116 * @param id
ec1f3444
NR
117 * the id of the value to get
118 *
119 * @return the associated value, or NULL if not found (not present in the
120 * resource file)
121 */
122 public String getString(E id) {
13bfeea6
NR
123 return getString(id, null);
124 }
125
126 /**
127 * Return the value associated to the given id as a {@link String}.
128 * <p>
129 * If no value is associated, take the default one if any.
130 *
131 * @param id
132 * the id of the value to get
133 * @param def
134 * the default value when it is not present in the config file
135 *
136 * @return the associated value, or NULL if not found (not present in the
137 * resource file)
138 */
139 public String getString(E id, String def) {
140 String rep = getString(id.name(), null);
141 if (rep == null) {
b15a4985
NR
142 try {
143 Meta meta = type.getDeclaredField(id.name()).getAnnotation(
144 Meta.class);
145 rep = meta.def();
146 } catch (NoSuchFieldException e) {
147 } catch (SecurityException e) {
148 }
13bfeea6
NR
149 }
150
151 if (rep == null) {
152 rep = def;
153 }
154
155 return rep;
ec1f3444
NR
156 }
157
62c9ec78
NR
158 /**
159 * Set the value associated to the given id as a {@link String}.
160 *
db31c358 161 * @param id
3bfc1d20 162 * the id of the value to set
62c9ec78
NR
163 * @param value
164 * the value
165 *
166 */
167 public void setString(E id, String value) {
487926f7 168 setString(id.name(), value);
62c9ec78
NR
169 }
170
ec1f3444
NR
171 /**
172 * Return the value associated to the given id as a {@link String} suffixed
173 * with the runtime value "_suffix" (that is, "_" and suffix).
487926f7
NR
174 * <p>
175 * Will only accept suffixes that form an existing id.
13bfeea6
NR
176 * <p>
177 * If no value is associated, take the default one if any.
ec1f3444 178 *
db31c358 179 * @param id
ec1f3444
NR
180 * the id of the value to get
181 * @param suffix
182 * the runtime suffix
183 *
184 * @return the associated value, or NULL if not found (not present in the
185 * resource file)
186 */
187 public String getStringX(E id, String suffix) {
188 String key = id.name()
80383c14 189 + (suffix == null ? "" : "_" + suffix.toUpperCase());
ec1f3444 190
487926f7
NR
191 try {
192 id = Enum.valueOf(type, key);
193 return getString(id);
194 } catch (IllegalArgumentException e) {
195
ec1f3444
NR
196 }
197
198 return null;
199 }
200
62c9ec78
NR
201 /**
202 * Set the value associated to the given id as a {@link String} suffixed
203 * with the runtime value "_suffix" (that is, "_" and suffix).
487926f7
NR
204 * <p>
205 * Will only accept suffixes that form an existing id.
62c9ec78 206 *
db31c358 207 * @param id
3bfc1d20 208 * the id of the value to set
62c9ec78
NR
209 * @param suffix
210 * the runtime suffix
211 * @param value
212 * the value
213 */
214 public void setStringX(E id, String suffix, String value) {
215 String key = id.name()
216 + (suffix == null ? "" : "_" + suffix.toUpperCase());
217
487926f7
NR
218 try {
219 id = Enum.valueOf(type, key);
220 setString(id, value);
221 } catch (IllegalArgumentException e) {
222
223 }
62c9ec78
NR
224 }
225
ec1f3444
NR
226 /**
227 * Return the value associated to the given id as a {@link Boolean}.
13bfeea6
NR
228 * <p>
229 * If no value is associated, take the default one if any.
ec1f3444 230 *
db31c358 231 * @param id
ec1f3444
NR
232 * the id of the value to get
233 *
234 * @return the associated value
235 */
236 public Boolean getBoolean(E id) {
237 String str = getString(id);
3bfc1d20 238 return BundleHelper.parseBoolean(str);
ec1f3444
NR
239 }
240
241 /**
62c9ec78 242 * Return the value associated to the given id as a {@link Boolean}.
13bfeea6
NR
243 * <p>
244 * If no value is associated, take the default one if any.
ec1f3444 245 *
db31c358 246 * @param id
ec1f3444
NR
247 * the id of the value to get
248 * @param def
249 * the default value when it is not present in the config file or
250 * if it is not a boolean value
251 *
252 * @return the associated value
253 */
254 public boolean getBoolean(E id, boolean def) {
255 Boolean b = getBoolean(id);
256 if (b != null)
257 return b;
258
259 return def;
260 }
d18e136e 261
3bfc1d20
NR
262 /**
263 * Set the value associated to the given id as a {@link Boolean}.
264 *
265 * @param id
266 * the id of the value to set
267 * @param value
268 * the value
269 *
270 */
271 public void setBoolean(E id, boolean value) {
272 setString(id.name(), BundleHelper.fromBoolean(value));
273 }
274
ec1f3444
NR
275 /**
276 * Return the value associated to the given id as an {@link Integer}.
13bfeea6
NR
277 * <p>
278 * If no value is associated, take the default one if any.
ec1f3444 279 *
db31c358 280 * @param id
ec1f3444
NR
281 * the id of the value to get
282 *
283 * @return the associated value
284 */
285 public Integer getInteger(E id) {
3bfc1d20 286 return BundleHelper.parseInteger(getString(id));
ec1f3444
NR
287 }
288
289 /**
db31c358 290 * Return the value associated to the given id as an int.
13bfeea6
NR
291 * <p>
292 * If no value is associated, take the default one if any.
ec1f3444 293 *
db31c358 294 * @param id
ec1f3444
NR
295 * the id of the value to get
296 * @param def
297 * the default value when it is not present in the config file or
298 * if it is not a int value
299 *
300 * @return the associated value
301 */
302 public int getInteger(E id, int def) {
303 Integer i = getInteger(id);
304 if (i != null)
305 return i;
306
307 return def;
308 }
309
3bfc1d20
NR
310 /**
311 * Set the value associated to the given id as a {@link Integer}.
312 *
313 * @param id
314 * the id of the value to set
315 * @param value
316 * the value
317 *
318 */
319 public void setInteger(E id, int value) {
320 setString(id.name(), BundleHelper.fromInteger(value));
321 }
d18e136e 322
ec1f3444
NR
323 /**
324 * Return the value associated to the given id as a {@link Character}.
13bfeea6
NR
325 * <p>
326 * If no value is associated, take the default one if any.
ec1f3444 327 *
db31c358 328 * @param id
ec1f3444
NR
329 * the id of the value to get
330 *
331 * @return the associated value
332 */
62c9ec78 333 public Character getCharacter(E id) {
3bfc1d20 334 return BundleHelper.parseCharacter(getString(id));
62c9ec78
NR
335 }
336
337 /**
338 * Return the value associated to the given id as a {@link Character}.
13bfeea6
NR
339 * <p>
340 * If no value is associated, take the default one if any.
62c9ec78 341 *
db31c358 342 * @param id
62c9ec78
NR
343 * the id of the value to get
344 * @param def
345 * the default value when it is not present in the config file or
346 * if it is not a char value
347 *
348 * @return the associated value
349 */
350 public char getCharacter(E id, char def) {
3bfc1d20
NR
351 Character car = getCharacter(id);
352 if (car != null)
353 return car;
62c9ec78
NR
354
355 return def;
ec1f3444
NR
356 }
357
b3aad1f9 358 /**
80500544
NR
359 * Return the value associated to the given id as a colour if it is found
360 * and can be parsed.
361 * <p>
362 * The returned value is an ARGB value.
13bfeea6
NR
363 * <p>
364 * If no value is associated, take the default one if any.
b3aad1f9 365 *
db31c358
NR
366 * @param id
367 * the id of the value to get
b3aad1f9
NR
368 *
369 * @return the associated value
370 */
80500544 371 public Integer getColor(E id) {
3bfc1d20 372 return BundleHelper.parseColor(getString(id));
b3aad1f9
NR
373 }
374
62c9ec78 375 /**
80500544
NR
376 * Set the value associated to the given id as a colour.
377 * <p>
3bfc1d20 378 * The value is a BGRA value.
62c9ec78 379 *
db31c358
NR
380 * @param id
381 * the id of the value to set
382 * @param color
80500544 383 * the new colour
62c9ec78 384 */
80500544 385 public void setColor(E id, Integer color) {
9e834013 386 setString(id, BundleHelper.fromColor(color));
62c9ec78
NR
387 }
388
7c192d3c
NR
389 /**
390 * Return the value associated to the given id as a list of values if it is
391 * found and can be parsed.
13bfeea6
NR
392 * <p>
393 * If no value is associated, take the default one if any.
7c192d3c
NR
394 *
395 * @param id
396 * the id of the value to get
397 *
398 * @return the associated list, empty if the value is empty, NULL if it is
399 * not found or cannot be parsed as a list
400 */
401 public List<String> getList(E id) {
3bfc1d20 402 return BundleHelper.parseList(getString(id));
7c192d3c
NR
403 }
404
405 /**
406 * Set the value associated to the given id as a list of values.
407 *
408 * @param id
409 * the id of the value to set
410 * @param list
411 * the new list of values
412 */
413 public void setList(E id, List<String> list) {
3bfc1d20 414 setString(id, BundleHelper.fromList(list));
7c192d3c
NR
415 }
416
487926f7
NR
417 /**
418 * Create/update the .properties file.
419 * <p>
420 * Will use the most likely candidate as base if the file does not already
421 * exists and this resource is translatable (for instance, "en_US" will use
422 * "en" as a base if the resource is a translation file).
423 * <p>
424 * Will update the files in {@link Bundles#getDirectory()}; it <b>MUST</b>
425 * be set.
426 *
427 * @throws IOException
428 * in case of IO errors
429 */
430 public void updateFile() throws IOException {
431 updateFile(Bundles.getDirectory());
432 }
433
ec1f3444 434 /**
80383c14
NR
435 * Create/update the .properties file.
436 * <p>
437 * Will use the most likely candidate as base if the file does not already
438 * exists and this resource is translatable (for instance, "en_US" will use
439 * "en" as a base if the resource is a translation file).
ec1f3444
NR
440 *
441 * @param path
487926f7
NR
442 * the path where the .properties files are, <b>MUST NOT</b> be
443 * NULL
ec1f3444
NR
444 *
445 * @throws IOException
446 * in case of IO errors
447 */
448 public void updateFile(String path) throws IOException {
449 File file = getUpdateFile(path);
450
451 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
452 new FileOutputStream(file), "UTF-8"));
453
454 writeHeader(writer);
455 writer.write("\n");
456 writer.write("\n");
457
458 for (Field field : type.getDeclaredFields()) {
459 Meta meta = field.getAnnotation(Meta.class);
460 if (meta != null) {
cd0c27d2 461 E id = Enum.valueOf(type, field.getName());
ec1f3444
NR
462 String info = getMetaInfo(meta);
463
464 if (info != null) {
465 writer.write(info);
466 writer.write("\n");
467 }
468
469 writeValue(writer, id);
470 }
471 }
472
473 writer.close();
474 }
475
e8aa5bf9
NR
476 /**
477 * Delete the .properties file.
478 * <p>
479 * Will use the most likely candidate as base if the file does not already
480 * exists and this resource is translatable (for instance, "en_US" will use
481 * "en" as a base if the resource is a translation file).
482 * <p>
483 * Will delete the files in {@link Bundles#getDirectory()}; it <b>MUST</b>
484 * be set.
485 *
486 * @return TRUE if the file was deleted
487 */
488 public boolean deleteFile() {
489 return deleteFile(Bundles.getDirectory());
490 }
491
492 /**
493 * Delete the .properties file.
494 * <p>
495 * Will use the most likely candidate as base if the file does not already
496 * exists and this resource is translatable (for instance, "en_US" will use
497 * "en" as a base if the resource is a translation file).
498 *
499 * @param path
500 * the path where the .properties files are, <b>MUST NOT</b> be
501 * NULL
502 *
503 * @return TRUE if the file was deleted
504 */
505 public boolean deleteFile(String path) {
506 File file = getUpdateFile(path);
507 return file.delete();
508 }
509
db31c358
NR
510 /**
511 * The description {@link TransBundle}, that is, a {@link TransBundle}
512 * dedicated to the description of the values of the given {@link Bundle}
513 * (can be NULL).
514 *
515 * @return the description {@link TransBundle}
516 */
517 public TransBundle<E> getDescriptionBundle() {
518 return descriptionBundle;
519 }
520
80383c14
NR
521 /**
522 * Reload the {@link Bundle} data files.
e9ca6bb8
NR
523 *
524 * @param resetToDefault
525 * reset to the default configuration (do not look into the
526 * possible user configuration files, only take the original
527 * configuration)
80383c14 528 */
e9ca6bb8 529 public void reload(boolean resetToDefault) {
db31c358 530 setBundle(keyType, Locale.getDefault(), resetToDefault);
80383c14
NR
531 }
532
ec1f3444
NR
533 /**
534 * Check if the internal map contains the given key.
535 *
536 * @param key
537 * the key to check for
538 *
539 * @return true if it does
540 */
541 protected boolean containsKey(String key) {
487926f7 542 return changeMap.containsKey(key) || map.containsKey(key);
ec1f3444
NR
543 }
544
545 /**
13bfeea6
NR
546 * Get the value for the given key if it exists in the internal map, or
547 * <tt>def</tt> if not.
ec1f3444
NR
548 *
549 * @param key
550 * the key to check for
13bfeea6
NR
551 * @param def
552 * the default value when it is not present in the internal map
ec1f3444 553 *
13bfeea6 554 * @return the value, or <tt>def</tt> if not found
ec1f3444 555 */
13bfeea6 556 protected String getString(String key, String def) {
62c9ec78
NR
557 if (changeMap.containsKey(key)) {
558 return changeMap.get(key);
559 }
560
487926f7
NR
561 if (map.containsKey(key)) {
562 return map.get(key);
ec1f3444
NR
563 }
564
13bfeea6 565 return def;
ec1f3444
NR
566 }
567
62c9ec78
NR
568 /**
569 * Set the value for this key, in the change map (it is kept in memory, not
570 * yet on disk).
571 *
572 * @param key
573 * the key
574 * @param value
575 * the associated value
576 */
577 protected void setString(String key, String value) {
487926f7 578 changeMap.put(key, value == null ? null : value.trim());
62c9ec78
NR
579 }
580
ec1f3444
NR
581 /**
582 * Return formated, display-able information from the {@link Meta} field
583 * given. Each line will always starts with a "#" character.
584 *
585 * @param meta
586 * the {@link Meta} field
587 *
588 * @return the information to display or NULL if none
589 */
590 protected String getMetaInfo(Meta meta) {
db31c358
NR
591 String desc = meta.description();
592 boolean group = meta.group();
593 Meta.Format format = meta.format();
594 String[] list = meta.list();
595 boolean nullable = meta.nullable();
e8aa5bf9 596 String def = meta.def();
db31c358 597 boolean array = meta.array();
ec1f3444 598
db31c358 599 // Default, empty values -> NULL
d18e136e 600 if (desc.length() + list.length + def.length() == 0 && !group
b15a4985 601 && nullable && format == Format.STRING) {
ec1f3444 602 return null;
db31c358 603 }
ec1f3444
NR
604
605 StringBuilder builder = new StringBuilder();
b15a4985
NR
606 for (String line : desc.split("\n")) {
607 builder.append("# ").append(line).append("\n");
db31c358 608 }
ec1f3444 609
db31c358 610 if (group) {
b15a4985 611 builder.append("# This item is used as a group, its content is not expected to be used.");
db31c358 612 } else {
b15a4985
NR
613 builder.append("# (FORMAT: ").append(format)
614 .append(nullable ? "" : ", required");
d18e136e 615 builder.append(") ");
db31c358
NR
616
617 if (list.length > 0) {
b15a4985
NR
618 builder.append("\n# ALLOWED VALUES: ");
619 boolean first = true;
db31c358 620 for (String value : list) {
b15a4985
NR
621 if (!first) {
622 builder.append(", ");
623 }
624 builder.append(BundleHelper.escape(value));
625 first = false;
db31c358 626 }
ec1f3444
NR
627 }
628
db31c358 629 if (array) {
b15a4985 630 builder.append("\n# (This item accepts a list of escaped comma-separated values)");
ec1f3444
NR
631 }
632 }
633
ec1f3444
NR
634 return builder.toString();
635 }
636
637 /**
638 * The display name used in the <tt>.properties file</tt>.
639 *
640 * @return the name
641 */
642 protected String getBundleDisplayName() {
db31c358 643 return keyType.toString();
ec1f3444
NR
644 }
645
646 /**
647 * Write the header found in the configuration <tt>.properties</tt> file of
648 * this {@link Bundles}.
649 *
650 * @param writer
651 * the {@link Writer} to write the header in
652 *
653 * @throws IOException
654 * in case of IO error
655 */
656 protected void writeHeader(Writer writer) throws IOException {
657 writer.write("# " + getBundleDisplayName() + "\n");
658 writer.write("#\n");
659 }
660
661 /**
b15a4985
NR
662 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
663 * followed by a new line.
664 * <p>
665 * Will prepend a # sign if the is is not set (see
666 * {@link Bundle#isSet(Enum, boolean)}).
ec1f3444
NR
667 *
668 * @param writer
669 * the {@link Writer} to write into
670 * @param id
671 * the id to write
672 *
673 * @throws IOException
674 * in case of IO error
675 */
676 protected void writeValue(Writer writer, E id) throws IOException {
b15a4985
NR
677 boolean set = isSet(id, false);
678 writeValue(writer, id.name(), getString(id), set);
ec1f3444
NR
679 }
680
681 /**
682 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
b15a4985
NR
683 * followed by a new line.
684 * <p>
685 * Will prepend a # sign if the is is not set.
ec1f3444
NR
686 *
687 * @param writer
688 * the {@link Writer} to write into
689 * @param id
690 * the id to write
691 * @param value
692 * the id's value
b15a4985
NR
693 * @param set
694 * the value is set in this {@link Bundle}
ec1f3444
NR
695 *
696 * @throws IOException
697 * in case of IO error
698 */
b15a4985
NR
699 protected void writeValue(Writer writer, String id, String value,
700 boolean set) throws IOException {
701
702 if (!set) {
703 writer.write('#');
704 }
705
ec1f3444
NR
706 writer.write(id);
707 writer.write(" = ");
708
709 if (value == null) {
710 value = "";
711 }
712
009196a4 713 String[] lines = value.replaceAll("\t", "\\\\\\t").split("\n");
ec1f3444
NR
714 for (int i = 0; i < lines.length; i++) {
715 writer.write(lines[i]);
716 if (i < lines.length - 1) {
717 writer.write("\\n\\");
718 }
719 writer.write("\n");
720 }
721 }
722
723 /**
724 * Return the source file for this {@link Bundles} from the given path.
725 *
726 * @param path
727 * the path where the .properties files are
728 *
729 * @return the source {@link File}
ec1f3444
NR
730 */
731 protected File getUpdateFile(String path) {
db31c358 732 return new File(path, keyType.name() + ".properties");
ec1f3444
NR
733 }
734
735 /**
62c9ec78 736 * Change the currently used bundle, and reset all changes.
ec1f3444
NR
737 *
738 * @param name
739 * the name of the bundle to load
740 * @param locale
741 * the {@link Locale} to use
e9ca6bb8
NR
742 * @param resetToDefault
743 * reset to the default configuration (do not look into the
744 * possible user configuration files, only take the original
745 * configuration)
ec1f3444 746 */
e9ca6bb8 747 protected void setBundle(Enum<?> name, Locale locale, boolean resetToDefault) {
62c9ec78 748 changeMap.clear();
ec1f3444 749 String dir = Bundles.getDirectory();
e8aa5bf9 750 String bname = type.getPackage().getName() + "." + name.name();
ec1f3444 751
487926f7 752 boolean found = false;
e9ca6bb8 753 if (!resetToDefault && dir != null) {
e8aa5bf9 754 // Look into Bundles.getDirectory() for .properties files
ec1f3444
NR
755 try {
756 File file = getPropertyFile(dir, name.name(), locale);
757 if (file != null) {
758 Reader reader = new InputStreamReader(new FileInputStream(
b15a4985 759 file), "UTF-8");
487926f7
NR
760 resetMap(new PropertyResourceBundle(reader));
761 found = true;
ec1f3444
NR
762 }
763 } catch (IOException e) {
764 e.printStackTrace();
765 }
766 }
767
487926f7 768 if (!found) {
e8aa5bf9 769 // Look into the package itself for resources
e9ca6bb8 770 try {
487926f7
NR
771 resetMap(ResourceBundle
772 .getBundle(bname, locale, type.getClassLoader(),
773 new FixedResourceBundleControl()));
e8aa5bf9
NR
774 found = true;
775 } catch (MissingResourceException e) {
e9ca6bb8 776 } catch (Exception e) {
e8aa5bf9 777 e.printStackTrace();
487926f7
NR
778 }
779 }
e8aa5bf9
NR
780
781 if (!found) {
782 // We have no bundle for this Bundle
783 System.err.println("No bundle found for: " + bname);
784 resetMap(null);
785 }
487926f7
NR
786 }
787
788 /**
e8aa5bf9 789 * Reset the backing map to the content of the given bundle, or with default
b15a4985 790 * values if bundle is NULL.
487926f7
NR
791 *
792 * @param bundle
793 * the bundle to copy
794 */
b771aed5 795 protected void resetMap(ResourceBundle bundle) {
487926f7 796 this.map.clear();
e8aa5bf9
NR
797 for (Field field : type.getDeclaredFields()) {
798 try {
799 Meta meta = field.getAnnotation(Meta.class);
800 if (meta != null) {
801 E id = Enum.valueOf(type, field.getName());
802
803 String value;
804 if (bundle != null) {
805 value = bundle.getString(id.name());
806 } else {
807 value = meta.def();
808 }
809
810 this.map.put(id.name(), value == null ? null : value.trim());
487926f7 811 }
e8aa5bf9 812 } catch (MissingResourceException e) {
e9ca6bb8
NR
813 }
814 }
815 }
cd0c27d2 816
e9ca6bb8
NR
817 /**
818 * Take a snapshot of the changes in memory in this {@link Bundle} made by
819 * the "set" methods ( {@link Bundle#setString(Enum, String)}...) at the
820 * current time.
821 *
487926f7 822 * @return a snapshot to use with {@link Bundle#restoreSnapshot(Object)}
e9ca6bb8 823 */
487926f7 824 public Object takeSnapshot() {
e9ca6bb8
NR
825 return new HashMap<String, String>(changeMap);
826 }
827
828 /**
829 * Restore a snapshot taken with {@link Bundle}, or reset the current
830 * changes if the snapshot is NULL.
831 *
832 * @param snap
833 * the snapshot or NULL
834 */
835 @SuppressWarnings("unchecked")
487926f7 836 public void restoreSnapshot(Object snap) {
e9ca6bb8
NR
837 if (snap == null) {
838 changeMap.clear();
839 } else {
840 if (snap instanceof Map) {
841 changeMap = (Map<String, String>) snap;
842 } else {
d827da2a 843 throw new RuntimeException(
e9ca6bb8
NR
844 "Restoring changes in a Bundle must be done on a changes snapshot, "
845 + "or NULL to discard current changes");
846 }
ec1f3444
NR
847 }
848 }
849
850 /**
851 * Return the resource file that is closer to the {@link Locale}.
852 *
853 * @param dir
db31c358 854 * the directory to look into
ec1f3444 855 * @param name
db31c358 856 * the file base name (without <tt>.properties</tt>)
ec1f3444
NR
857 * @param locale
858 * the {@link Locale}
859 *
860 * @return the closest match or NULL if none
861 */
862 private File getPropertyFile(String dir, String name, Locale locale) {
863 List<String> locales = new ArrayList<String>();
864 if (locale != null) {
865 String country = locale.getCountry() == null ? "" : locale
866 .getCountry();
867 String language = locale.getLanguage() == null ? "" : locale
868 .getLanguage();
869 if (!language.isEmpty() && !country.isEmpty()) {
870 locales.add("_" + language + "-" + country);
871 }
872 if (!language.isEmpty()) {
873 locales.add("_" + language);
874 }
875 }
876
877 locales.add("");
878
879 File file = null;
880 for (String loc : locales) {
881 file = new File(dir, name + loc + ".properties");
882 if (file.exists()) {
883 break;
ec1f3444 884 }
d827da2a 885
cd0c27d2 886 file = null;
ec1f3444
NR
887 }
888
889 return file;
890 }
891}