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