Commit | Line | Data |
---|---|---|
ec1f3444 NR |
1 | package be.nikiroo.utils.resources; |
2 | ||
3 | import java.io.BufferedWriter; | |
4 | import java.io.File; | |
5 | import java.io.FileInputStream; | |
6 | import java.io.FileOutputStream; | |
7 | import java.io.IOException; | |
8 | import java.io.InputStreamReader; | |
9 | import java.io.OutputStreamWriter; | |
10 | import java.io.Reader; | |
ec1f3444 NR |
11 | import java.io.Writer; |
12 | import java.lang.reflect.Field; | |
13 | import java.util.ArrayList; | |
62c9ec78 | 14 | import java.util.HashMap; |
ec1f3444 NR |
15 | import java.util.List; |
16 | import java.util.Locale; | |
62c9ec78 | 17 | import java.util.Map; |
ec1f3444 NR |
18 | import java.util.MissingResourceException; |
19 | import java.util.PropertyResourceBundle; | |
20 | import java.util.ResourceBundle; | |
21 | ||
b15a4985 NR |
22 | import 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 | 38 | public 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) { | |
d5026c09 NR |
140 | return getString(id, def, -1); |
141 | } | |
142 | ||
143 | /** | |
144 | * Return the value associated to the given id as a {@link String}. | |
145 | * <p> | |
24604392 NR |
146 | * If no value is associated (or if it is empty!), take the default one if |
147 | * any. | |
d5026c09 NR |
148 | * |
149 | * @param id | |
150 | * the id of the value to get | |
151 | * @param def | |
152 | * the default value when it is not present in the config file | |
153 | * @param item | |
154 | * the item number to get for an array of values, or -1 for | |
155 | * non-arrays | |
156 | * | |
157 | * @return the associated value, or NULL if not found (not present in the | |
158 | * resource file) | |
159 | */ | |
160 | public String getString(E id, String def, int item) { | |
13bfeea6 NR |
161 | String rep = getString(id.name(), null); |
162 | if (rep == null) { | |
b15a4985 NR |
163 | try { |
164 | Meta meta = type.getDeclaredField(id.name()).getAnnotation( | |
165 | Meta.class); | |
166 | rep = meta.def(); | |
167 | } catch (NoSuchFieldException e) { | |
168 | } catch (SecurityException e) { | |
169 | } | |
13bfeea6 NR |
170 | } |
171 | ||
d5026c09 NR |
172 | if (rep == null || rep.isEmpty()) { |
173 | return def; | |
174 | } | |
175 | ||
176 | if (item >= 0) { | |
177 | List<String> values = BundleHelper.parseList(rep, item); | |
178 | if (values != null && item < values.size()) { | |
179 | return values.get(item); | |
180 | } | |
181 | ||
182 | return null; | |
13bfeea6 NR |
183 | } |
184 | ||
185 | return rep; | |
ec1f3444 NR |
186 | } |
187 | ||
62c9ec78 NR |
188 | /** |
189 | * Set the value associated to the given id as a {@link String}. | |
190 | * | |
db31c358 | 191 | * @param id |
3bfc1d20 | 192 | * the id of the value to set |
62c9ec78 NR |
193 | * @param value |
194 | * the value | |
195 | * | |
196 | */ | |
197 | public void setString(E id, String value) { | |
487926f7 | 198 | setString(id.name(), value); |
62c9ec78 NR |
199 | } |
200 | ||
d5026c09 NR |
201 | /** |
202 | * Set the value associated to the given id as a {@link String}. | |
203 | * | |
204 | * @param id | |
205 | * the id of the value to set | |
206 | * @param value | |
207 | * the value | |
208 | * @param item | |
209 | * the item number to get for an array of values, or -1 for | |
210 | * non-arrays | |
211 | * | |
212 | */ | |
213 | public void setString(E id, String value, int item) { | |
214 | if (item < 0) { | |
215 | setString(id.name(), value); | |
216 | } else { | |
217 | List<String> values = getList(id); | |
218 | for (int i = values.size(); i < item; i++) { | |
219 | values.add(null); | |
220 | } | |
221 | values.set(item, value); | |
222 | setString(id.name(), BundleHelper.fromList(values)); | |
223 | } | |
224 | } | |
225 | ||
ec1f3444 NR |
226 | /** |
227 | * Return the value associated to the given id as a {@link String} suffixed | |
228 | * with the runtime value "_suffix" (that is, "_" and suffix). | |
487926f7 NR |
229 | * <p> |
230 | * Will only accept suffixes that form an existing id. | |
13bfeea6 NR |
231 | * <p> |
232 | * If no value is associated, take the default one if any. | |
ec1f3444 | 233 | * |
db31c358 | 234 | * @param id |
ec1f3444 NR |
235 | * the id of the value to get |
236 | * @param suffix | |
237 | * the runtime suffix | |
238 | * | |
239 | * @return the associated value, or NULL if not found (not present in the | |
240 | * resource file) | |
241 | */ | |
242 | public String getStringX(E id, String suffix) { | |
d5026c09 NR |
243 | return getStringX(id, suffix, null, -1); |
244 | } | |
245 | ||
246 | /** | |
247 | * Return the value associated to the given id as a {@link String} suffixed | |
248 | * with the runtime value "_suffix" (that is, "_" and suffix). | |
249 | * <p> | |
250 | * Will only accept suffixes that form an existing id. | |
251 | * <p> | |
252 | * If no value is associated, take the default one if any. | |
253 | * | |
254 | * @param id | |
255 | * the id of the value to get | |
256 | * @param suffix | |
257 | * the runtime suffix | |
258 | * @param def | |
259 | * the default value when it is not present in the config file | |
260 | * | |
261 | * @return the associated value, or NULL if not found (not present in the | |
262 | * resource file) | |
263 | */ | |
264 | public String getStringX(E id, String suffix, String def) { | |
265 | return getStringX(id, suffix, def, -1); | |
266 | } | |
267 | ||
268 | /** | |
269 | * Return the value associated to the given id as a {@link String} suffixed | |
270 | * with the runtime value "_suffix" (that is, "_" and suffix). | |
271 | * <p> | |
272 | * Will only accept suffixes that form an existing id. | |
273 | * <p> | |
274 | * If no value is associated, take the default one if any. | |
275 | * | |
276 | * @param id | |
277 | * the id of the value to get | |
278 | * @param suffix | |
279 | * the runtime suffix | |
280 | * @param item | |
281 | * the item number to get for an array of values, or -1 for | |
282 | * non-arrays | |
283 | * @param def | |
284 | * the default value when it is not present in the config file | |
285 | * @param item | |
286 | * the item number to get for an array of values, or -1 for | |
287 | * non-arrays | |
288 | * | |
289 | * @return the associated value, or NULL if not found (not present in the | |
290 | * resource file) | |
291 | */ | |
292 | public String getStringX(E id, String suffix, String def, int item) { | |
ec1f3444 | 293 | String key = id.name() |
80383c14 | 294 | + (suffix == null ? "" : "_" + suffix.toUpperCase()); |
ec1f3444 | 295 | |
487926f7 NR |
296 | try { |
297 | id = Enum.valueOf(type, key); | |
d5026c09 | 298 | return getString(id, def, item); |
487926f7 | 299 | } catch (IllegalArgumentException e) { |
ec1f3444 NR |
300 | } |
301 | ||
302 | return null; | |
303 | } | |
304 | ||
62c9ec78 NR |
305 | /** |
306 | * Set the value associated to the given id as a {@link String} suffixed | |
307 | * with the runtime value "_suffix" (that is, "_" and suffix). | |
487926f7 NR |
308 | * <p> |
309 | * Will only accept suffixes that form an existing id. | |
62c9ec78 | 310 | * |
db31c358 | 311 | * @param id |
3bfc1d20 | 312 | * the id of the value to set |
62c9ec78 NR |
313 | * @param suffix |
314 | * the runtime suffix | |
315 | * @param value | |
316 | * the value | |
317 | */ | |
318 | public void setStringX(E id, String suffix, String value) { | |
d5026c09 NR |
319 | setStringX(id, suffix, value, -1); |
320 | } | |
321 | ||
322 | /** | |
323 | * Set the value associated to the given id as a {@link String} suffixed | |
324 | * with the runtime value "_suffix" (that is, "_" and suffix). | |
325 | * <p> | |
326 | * Will only accept suffixes that form an existing id. | |
327 | * | |
328 | * @param id | |
329 | * the id of the value to set | |
330 | * @param suffix | |
331 | * the runtime suffix | |
332 | * @param value | |
333 | * the value | |
334 | * @param item | |
335 | * the item number to get for an array of values, or -1 for | |
336 | * non-arrays | |
337 | */ | |
338 | public void setStringX(E id, String suffix, String value, int item) { | |
62c9ec78 NR |
339 | String key = id.name() |
340 | + (suffix == null ? "" : "_" + suffix.toUpperCase()); | |
341 | ||
487926f7 NR |
342 | try { |
343 | id = Enum.valueOf(type, key); | |
d5026c09 | 344 | setString(id, value, item); |
487926f7 | 345 | } catch (IllegalArgumentException e) { |
487926f7 | 346 | } |
62c9ec78 NR |
347 | } |
348 | ||
ec1f3444 NR |
349 | /** |
350 | * Return the value associated to the given id as a {@link Boolean}. | |
13bfeea6 NR |
351 | * <p> |
352 | * If no value is associated, take the default one if any. | |
ec1f3444 | 353 | * |
db31c358 | 354 | * @param id |
ec1f3444 NR |
355 | * the id of the value to get |
356 | * | |
357 | * @return the associated value | |
358 | */ | |
359 | public Boolean getBoolean(E id) { | |
d5026c09 | 360 | return BundleHelper.parseBoolean(getString(id), -1); |
ec1f3444 NR |
361 | } |
362 | ||
363 | /** | |
62c9ec78 | 364 | * Return the value associated to the given id as a {@link Boolean}. |
13bfeea6 NR |
365 | * <p> |
366 | * If no value is associated, take the default one if any. | |
ec1f3444 | 367 | * |
db31c358 | 368 | * @param id |
ec1f3444 NR |
369 | * the id of the value to get |
370 | * @param def | |
371 | * the default value when it is not present in the config file or | |
372 | * if it is not a boolean value | |
373 | * | |
374 | * @return the associated value | |
375 | */ | |
376 | public boolean getBoolean(E id, boolean def) { | |
d5026c09 NR |
377 | Boolean value = getBoolean(id); |
378 | if (value != null) { | |
379 | return value; | |
380 | } | |
381 | ||
382 | return def; | |
383 | } | |
384 | ||
385 | /** | |
386 | * Return the value associated to the given id as a {@link Boolean}. | |
387 | * <p> | |
388 | * If no value is associated, take the default one if any. | |
389 | * | |
390 | * @param id | |
391 | * the id of the value to get | |
392 | * @param def | |
393 | * the default value when it is not present in the config file or | |
394 | * if it is not a boolean value | |
395 | * @param item | |
396 | * the item number to get for an array of values, or -1 for | |
397 | * non-arrays | |
398 | * | |
399 | * @return the associated value | |
400 | */ | |
401 | public Boolean getBoolean(E id, boolean def, int item) { | |
402 | String value = getString(id); | |
403 | if (value != null) { | |
404 | return BundleHelper.parseBoolean(value, item); | |
405 | } | |
ec1f3444 NR |
406 | |
407 | return def; | |
408 | } | |
d18e136e | 409 | |
3bfc1d20 NR |
410 | /** |
411 | * Set the value associated to the given id as a {@link Boolean}. | |
412 | * | |
413 | * @param id | |
414 | * the id of the value to set | |
415 | * @param value | |
416 | * the value | |
417 | * | |
418 | */ | |
419 | public void setBoolean(E id, boolean value) { | |
d5026c09 NR |
420 | setBoolean(id, value, -1); |
421 | } | |
422 | ||
423 | /** | |
424 | * Set the value associated to the given id as a {@link Boolean}. | |
425 | * | |
426 | * @param id | |
427 | * the id of the value to set | |
428 | * @param value | |
429 | * the value | |
430 | * @param item | |
431 | * the item number to get for an array of values, or -1 for | |
432 | * non-arrays | |
433 | * | |
434 | */ | |
435 | public void setBoolean(E id, boolean value, int item) { | |
436 | setString(id, BundleHelper.fromBoolean(value), item); | |
3bfc1d20 NR |
437 | } |
438 | ||
ec1f3444 NR |
439 | /** |
440 | * Return the value associated to the given id as an {@link Integer}. | |
13bfeea6 NR |
441 | * <p> |
442 | * If no value is associated, take the default one if any. | |
ec1f3444 | 443 | * |
db31c358 | 444 | * @param id |
ec1f3444 NR |
445 | * the id of the value to get |
446 | * | |
447 | * @return the associated value | |
448 | */ | |
449 | public Integer getInteger(E id) { | |
d5026c09 NR |
450 | String value = getString(id); |
451 | if (value != null) { | |
452 | return BundleHelper.parseInteger(value, -1); | |
453 | } | |
454 | ||
455 | return null; | |
ec1f3444 NR |
456 | } |
457 | ||
458 | /** | |
db31c358 | 459 | * Return the value associated to the given id as an int. |
13bfeea6 NR |
460 | * <p> |
461 | * If no value is associated, take the default one if any. | |
ec1f3444 | 462 | * |
db31c358 | 463 | * @param id |
ec1f3444 NR |
464 | * the id of the value to get |
465 | * @param def | |
466 | * the default value when it is not present in the config file or | |
467 | * if it is not a int value | |
468 | * | |
469 | * @return the associated value | |
470 | */ | |
471 | public int getInteger(E id, int def) { | |
d5026c09 NR |
472 | Integer value = getInteger(id); |
473 | if (value != null) { | |
474 | return value; | |
475 | } | |
476 | ||
477 | return def; | |
478 | } | |
479 | ||
480 | /** | |
481 | * Return the value associated to the given id as an int. | |
482 | * <p> | |
483 | * If no value is associated, take the default one if any. | |
484 | * | |
485 | * @param id | |
486 | * the id of the value to get | |
487 | * @param def | |
488 | * the default value when it is not present in the config file or | |
489 | * if it is not a int value | |
490 | * @param item | |
491 | * the item number to get for an array of values, or -1 for | |
492 | * non-arrays | |
493 | * | |
494 | * @return the associated value | |
495 | */ | |
496 | public Integer getInteger(E id, int def, int item) { | |
497 | String value = getString(id); | |
498 | if (value != null) { | |
499 | return BundleHelper.parseInteger(value, item); | |
500 | } | |
ec1f3444 NR |
501 | |
502 | return def; | |
503 | } | |
504 | ||
3bfc1d20 NR |
505 | /** |
506 | * Set the value associated to the given id as a {@link Integer}. | |
507 | * | |
508 | * @param id | |
509 | * the id of the value to set | |
510 | * @param value | |
511 | * the value | |
512 | * | |
513 | */ | |
514 | public void setInteger(E id, int value) { | |
d5026c09 NR |
515 | setInteger(id, value, -1); |
516 | } | |
517 | ||
518 | /** | |
519 | * Set the value associated to the given id as a {@link Integer}. | |
520 | * | |
521 | * @param id | |
522 | * the id of the value to set | |
523 | * @param value | |
524 | * the value | |
525 | * @param item | |
526 | * the item number to get for an array of values, or -1 for | |
527 | * non-arrays | |
528 | * | |
529 | */ | |
530 | public void setInteger(E id, int value, int item) { | |
531 | setString(id, BundleHelper.fromInteger(value), item); | |
3bfc1d20 | 532 | } |
d18e136e | 533 | |
ec1f3444 NR |
534 | /** |
535 | * Return the value associated to the given id as a {@link Character}. | |
13bfeea6 NR |
536 | * <p> |
537 | * If no value is associated, take the default one if any. | |
ec1f3444 | 538 | * |
db31c358 | 539 | * @param id |
ec1f3444 NR |
540 | * the id of the value to get |
541 | * | |
542 | * @return the associated value | |
543 | */ | |
62c9ec78 | 544 | public Character getCharacter(E id) { |
d5026c09 | 545 | return BundleHelper.parseCharacter(getString(id), -1); |
62c9ec78 NR |
546 | } |
547 | ||
548 | /** | |
549 | * Return the value associated to the given id as a {@link Character}. | |
13bfeea6 NR |
550 | * <p> |
551 | * If no value is associated, take the default one if any. | |
62c9ec78 | 552 | * |
db31c358 | 553 | * @param id |
62c9ec78 NR |
554 | * the id of the value to get |
555 | * @param def | |
556 | * the default value when it is not present in the config file or | |
557 | * if it is not a char value | |
558 | * | |
559 | * @return the associated value | |
560 | */ | |
561 | public char getCharacter(E id, char def) { | |
d5026c09 NR |
562 | Character value = getCharacter(id); |
563 | if (value != null) { | |
564 | return value; | |
565 | } | |
566 | ||
567 | return def; | |
568 | } | |
569 | ||
570 | /** | |
571 | * Return the value associated to the given id as a {@link Character}. | |
572 | * <p> | |
573 | * If no value is associated, take the default one if any. | |
574 | * | |
575 | * @param id | |
576 | * the id of the value to get | |
577 | * @param def | |
578 | * the default value when it is not present in the config file or | |
579 | * if it is not a char value | |
580 | * | |
581 | * @return the associated value | |
582 | */ | |
583 | public Character getCharacter(E id, char def, int item) { | |
584 | String value = getString(id); | |
585 | if (value != null) { | |
586 | return BundleHelper.parseCharacter(value, item); | |
587 | } | |
62c9ec78 NR |
588 | |
589 | return def; | |
ec1f3444 NR |
590 | } |
591 | ||
d5026c09 NR |
592 | /** |
593 | * Set the value associated to the given id as a {@link Character}. | |
594 | * | |
595 | * @param id | |
596 | * the id of the value to set | |
597 | * @param value | |
598 | * the value | |
599 | * | |
600 | */ | |
601 | public void setCharacter(E id, char value) { | |
602 | setCharacter(id, value, -1); | |
603 | } | |
604 | ||
605 | /** | |
606 | * Set the value associated to the given id as a {@link Character}. | |
607 | * | |
608 | * @param id | |
609 | * the id of the value to set | |
610 | * @param value | |
611 | * the value | |
612 | * @param item | |
613 | * the item number to get for an array of values, or -1 for | |
614 | * non-arrays | |
615 | * | |
616 | */ | |
617 | public void setCharacter(E id, char value, int item) { | |
618 | setString(id, BundleHelper.fromCharacter(value), item); | |
619 | } | |
620 | ||
b3aad1f9 | 621 | /** |
80500544 NR |
622 | * Return the value associated to the given id as a colour if it is found |
623 | * and can be parsed. | |
624 | * <p> | |
625 | * The returned value is an ARGB value. | |
13bfeea6 NR |
626 | * <p> |
627 | * If no value is associated, take the default one if any. | |
b3aad1f9 | 628 | * |
db31c358 NR |
629 | * @param id |
630 | * the id of the value to get | |
b3aad1f9 NR |
631 | * |
632 | * @return the associated value | |
633 | */ | |
80500544 | 634 | public Integer getColor(E id) { |
d5026c09 NR |
635 | return BundleHelper.parseColor(getString(id), -1); |
636 | } | |
637 | ||
638 | /** | |
639 | * Return the value associated to the given id as a colour if it is found | |
640 | * and can be parsed. | |
641 | * <p> | |
642 | * The returned value is an ARGB value. | |
643 | * <p> | |
644 | * If no value is associated, take the default one if any. | |
645 | * | |
646 | * @param id | |
647 | * the id of the value to get | |
648 | * | |
649 | * @return the associated value | |
650 | */ | |
651 | public int getColor(E id, int def) { | |
652 | Integer value = getColor(id); | |
653 | if (value != null) { | |
654 | return value; | |
655 | } | |
656 | ||
657 | return def; | |
658 | } | |
659 | ||
660 | /** | |
661 | * Return the value associated to the given id as a colour if it is found | |
662 | * and can be parsed. | |
663 | * <p> | |
664 | * The returned value is an ARGB value. | |
665 | * <p> | |
666 | * If no value is associated, take the default one if any. | |
667 | * | |
668 | * @param id | |
669 | * the id of the value to get | |
670 | * | |
671 | * @return the associated value | |
672 | */ | |
673 | public Integer getColor(E id, int def, int item) { | |
674 | String value = getString(id); | |
675 | if (value != null) { | |
676 | return BundleHelper.parseColor(value, item); | |
677 | } | |
678 | ||
679 | return def; | |
b3aad1f9 NR |
680 | } |
681 | ||
62c9ec78 | 682 | /** |
80500544 NR |
683 | * Set the value associated to the given id as a colour. |
684 | * <p> | |
3bfc1d20 | 685 | * The value is a BGRA value. |
62c9ec78 | 686 | * |
db31c358 NR |
687 | * @param id |
688 | * the id of the value to set | |
689 | * @param color | |
80500544 | 690 | * the new colour |
62c9ec78 | 691 | */ |
80500544 | 692 | public void setColor(E id, Integer color) { |
d5026c09 NR |
693 | setColor(id, color, -1); |
694 | } | |
695 | ||
696 | /** | |
697 | * Set the value associated to the given id as a Color. | |
698 | * | |
699 | * @param id | |
700 | * the id of the value to set | |
701 | * @param value | |
702 | * the value | |
703 | * @param item | |
704 | * the item number to get for an array of values, or -1 for | |
705 | * non-arrays | |
706 | * | |
707 | */ | |
708 | public void setColor(E id, int value, int item) { | |
709 | setString(id, BundleHelper.fromColor(value), item); | |
62c9ec78 NR |
710 | } |
711 | ||
7c192d3c NR |
712 | /** |
713 | * Return the value associated to the given id as a list of values if it is | |
714 | * found and can be parsed. | |
13bfeea6 NR |
715 | * <p> |
716 | * If no value is associated, take the default one if any. | |
7c192d3c NR |
717 | * |
718 | * @param id | |
719 | * the id of the value to get | |
720 | * | |
721 | * @return the associated list, empty if the value is empty, NULL if it is | |
722 | * not found or cannot be parsed as a list | |
723 | */ | |
724 | public List<String> getList(E id) { | |
d5026c09 NR |
725 | return BundleHelper.parseList(getString(id), -1); |
726 | } | |
727 | ||
728 | /** | |
729 | * Return the value associated to the given id as a list of values if it is | |
730 | * found and can be parsed. | |
731 | * <p> | |
732 | * If no value is associated, take the default one if any. | |
733 | * | |
734 | * @param id | |
735 | * the id of the value to get | |
736 | * | |
737 | * @return the associated list, empty if the value is empty, NULL if it is | |
738 | * not found or cannot be parsed as a list | |
739 | */ | |
740 | public List<String> getList(E id, List<String> def) { | |
741 | List<String> value = getList(id); | |
742 | if (value != null) { | |
743 | return value; | |
744 | } | |
745 | ||
746 | return def; | |
747 | } | |
748 | ||
749 | /** | |
750 | * Return the value associated to the given id as a list of values if it is | |
751 | * found and can be parsed. | |
752 | * <p> | |
753 | * If no value is associated, take the default one if any. | |
754 | * | |
755 | * @param id | |
756 | * the id of the value to get | |
757 | * | |
758 | * @return the associated list, empty if the value is empty, NULL if it is | |
759 | * not found or cannot be parsed as a list | |
760 | */ | |
761 | public List<String> getList(E id, List<String> def, int item) { | |
762 | String value = getString(id); | |
763 | if (value != null) { | |
764 | return BundleHelper.parseList(value, item); | |
765 | } | |
766 | ||
767 | return def; | |
7c192d3c NR |
768 | } |
769 | ||
770 | /** | |
771 | * Set the value associated to the given id as a list of values. | |
772 | * | |
773 | * @param id | |
774 | * the id of the value to set | |
775 | * @param list | |
776 | * the new list of values | |
777 | */ | |
778 | public void setList(E id, List<String> list) { | |
d5026c09 NR |
779 | setList(id, list, -1); |
780 | } | |
781 | ||
782 | /** | |
783 | * Set the value associated to the given id as a {@link List}. | |
784 | * | |
785 | * @param id | |
786 | * the id of the value to set | |
787 | * @param value | |
788 | * the value | |
789 | * @param item | |
790 | * the item number to get for an array of values, or -1 for | |
791 | * non-arrays | |
792 | * | |
793 | */ | |
794 | public void setList(E id, List<String> value, int item) { | |
795 | setString(id, BundleHelper.fromList(value), item); | |
7c192d3c NR |
796 | } |
797 | ||
487926f7 NR |
798 | /** |
799 | * Create/update the .properties file. | |
800 | * <p> | |
801 | * Will use the most likely candidate as base if the file does not already | |
802 | * exists and this resource is translatable (for instance, "en_US" will use | |
803 | * "en" as a base if the resource is a translation file). | |
804 | * <p> | |
805 | * Will update the files in {@link Bundles#getDirectory()}; it <b>MUST</b> | |
806 | * be set. | |
807 | * | |
808 | * @throws IOException | |
809 | * in case of IO errors | |
810 | */ | |
811 | public void updateFile() throws IOException { | |
812 | updateFile(Bundles.getDirectory()); | |
813 | } | |
814 | ||
ec1f3444 | 815 | /** |
80383c14 NR |
816 | * Create/update the .properties file. |
817 | * <p> | |
818 | * Will use the most likely candidate as base if the file does not already | |
819 | * exists and this resource is translatable (for instance, "en_US" will use | |
820 | * "en" as a base if the resource is a translation file). | |
ec1f3444 NR |
821 | * |
822 | * @param path | |
487926f7 NR |
823 | * the path where the .properties files are, <b>MUST NOT</b> be |
824 | * NULL | |
ec1f3444 NR |
825 | * |
826 | * @throws IOException | |
827 | * in case of IO errors | |
828 | */ | |
829 | public void updateFile(String path) throws IOException { | |
830 | File file = getUpdateFile(path); | |
831 | ||
832 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( | |
833 | new FileOutputStream(file), "UTF-8")); | |
834 | ||
835 | writeHeader(writer); | |
836 | writer.write("\n"); | |
837 | writer.write("\n"); | |
838 | ||
839 | for (Field field : type.getDeclaredFields()) { | |
840 | Meta meta = field.getAnnotation(Meta.class); | |
841 | if (meta != null) { | |
cd0c27d2 | 842 | E id = Enum.valueOf(type, field.getName()); |
ec1f3444 NR |
843 | String info = getMetaInfo(meta); |
844 | ||
845 | if (info != null) { | |
846 | writer.write(info); | |
847 | writer.write("\n"); | |
848 | } | |
849 | ||
850 | writeValue(writer, id); | |
851 | } | |
852 | } | |
853 | ||
854 | writer.close(); | |
855 | } | |
856 | ||
e8aa5bf9 NR |
857 | /** |
858 | * Delete the .properties file. | |
859 | * <p> | |
860 | * Will use the most likely candidate as base if the file does not already | |
861 | * exists and this resource is translatable (for instance, "en_US" will use | |
862 | * "en" as a base if the resource is a translation file). | |
863 | * <p> | |
864 | * Will delete the files in {@link Bundles#getDirectory()}; it <b>MUST</b> | |
865 | * be set. | |
866 | * | |
867 | * @return TRUE if the file was deleted | |
868 | */ | |
869 | public boolean deleteFile() { | |
870 | return deleteFile(Bundles.getDirectory()); | |
871 | } | |
872 | ||
873 | /** | |
874 | * Delete the .properties file. | |
875 | * <p> | |
876 | * Will use the most likely candidate as base if the file does not already | |
877 | * exists and this resource is translatable (for instance, "en_US" will use | |
878 | * "en" as a base if the resource is a translation file). | |
879 | * | |
880 | * @param path | |
881 | * the path where the .properties files are, <b>MUST NOT</b> be | |
882 | * NULL | |
883 | * | |
884 | * @return TRUE if the file was deleted | |
885 | */ | |
886 | public boolean deleteFile(String path) { | |
887 | File file = getUpdateFile(path); | |
888 | return file.delete(); | |
889 | } | |
890 | ||
db31c358 NR |
891 | /** |
892 | * The description {@link TransBundle}, that is, a {@link TransBundle} | |
893 | * dedicated to the description of the values of the given {@link Bundle} | |
894 | * (can be NULL). | |
895 | * | |
896 | * @return the description {@link TransBundle} | |
897 | */ | |
898 | public TransBundle<E> getDescriptionBundle() { | |
899 | return descriptionBundle; | |
900 | } | |
901 | ||
80383c14 NR |
902 | /** |
903 | * Reload the {@link Bundle} data files. | |
e9ca6bb8 NR |
904 | * |
905 | * @param resetToDefault | |
906 | * reset to the default configuration (do not look into the | |
907 | * possible user configuration files, only take the original | |
908 | * configuration) | |
80383c14 | 909 | */ |
e9ca6bb8 | 910 | public void reload(boolean resetToDefault) { |
db31c358 | 911 | setBundle(keyType, Locale.getDefault(), resetToDefault); |
80383c14 NR |
912 | } |
913 | ||
ec1f3444 NR |
914 | /** |
915 | * Check if the internal map contains the given key. | |
916 | * | |
917 | * @param key | |
918 | * the key to check for | |
919 | * | |
920 | * @return true if it does | |
921 | */ | |
922 | protected boolean containsKey(String key) { | |
487926f7 | 923 | return changeMap.containsKey(key) || map.containsKey(key); |
ec1f3444 NR |
924 | } |
925 | ||
926 | /** | |
13bfeea6 NR |
927 | * Get the value for the given key if it exists in the internal map, or |
928 | * <tt>def</tt> if not. | |
ec1f3444 NR |
929 | * |
930 | * @param key | |
931 | * the key to check for | |
13bfeea6 NR |
932 | * @param def |
933 | * the default value when it is not present in the internal map | |
ec1f3444 | 934 | * |
13bfeea6 | 935 | * @return the value, or <tt>def</tt> if not found |
ec1f3444 | 936 | */ |
13bfeea6 | 937 | protected String getString(String key, String def) { |
62c9ec78 NR |
938 | if (changeMap.containsKey(key)) { |
939 | return changeMap.get(key); | |
940 | } | |
941 | ||
487926f7 NR |
942 | if (map.containsKey(key)) { |
943 | return map.get(key); | |
ec1f3444 NR |
944 | } |
945 | ||
13bfeea6 | 946 | return def; |
ec1f3444 NR |
947 | } |
948 | ||
62c9ec78 NR |
949 | /** |
950 | * Set the value for this key, in the change map (it is kept in memory, not | |
951 | * yet on disk). | |
952 | * | |
953 | * @param key | |
954 | * the key | |
955 | * @param value | |
956 | * the associated value | |
957 | */ | |
958 | protected void setString(String key, String value) { | |
487926f7 | 959 | changeMap.put(key, value == null ? null : value.trim()); |
62c9ec78 NR |
960 | } |
961 | ||
ec1f3444 NR |
962 | /** |
963 | * Return formated, display-able information from the {@link Meta} field | |
964 | * given. Each line will always starts with a "#" character. | |
965 | * | |
966 | * @param meta | |
967 | * the {@link Meta} field | |
968 | * | |
969 | * @return the information to display or NULL if none | |
970 | */ | |
971 | protected String getMetaInfo(Meta meta) { | |
db31c358 NR |
972 | String desc = meta.description(); |
973 | boolean group = meta.group(); | |
974 | Meta.Format format = meta.format(); | |
975 | String[] list = meta.list(); | |
976 | boolean nullable = meta.nullable(); | |
e8aa5bf9 | 977 | String def = meta.def(); |
db31c358 | 978 | boolean array = meta.array(); |
ec1f3444 | 979 | |
db31c358 | 980 | // Default, empty values -> NULL |
d18e136e | 981 | if (desc.length() + list.length + def.length() == 0 && !group |
b15a4985 | 982 | && nullable && format == Format.STRING) { |
ec1f3444 | 983 | return null; |
db31c358 | 984 | } |
ec1f3444 NR |
985 | |
986 | StringBuilder builder = new StringBuilder(); | |
b15a4985 NR |
987 | for (String line : desc.split("\n")) { |
988 | builder.append("# ").append(line).append("\n"); | |
db31c358 | 989 | } |
ec1f3444 | 990 | |
db31c358 | 991 | if (group) { |
b15a4985 | 992 | builder.append("# This item is used as a group, its content is not expected to be used."); |
db31c358 | 993 | } else { |
b15a4985 NR |
994 | builder.append("# (FORMAT: ").append(format) |
995 | .append(nullable ? "" : ", required"); | |
d18e136e | 996 | builder.append(") "); |
db31c358 NR |
997 | |
998 | if (list.length > 0) { | |
b15a4985 NR |
999 | builder.append("\n# ALLOWED VALUES: "); |
1000 | boolean first = true; | |
db31c358 | 1001 | for (String value : list) { |
b15a4985 NR |
1002 | if (!first) { |
1003 | builder.append(", "); | |
1004 | } | |
1005 | builder.append(BundleHelper.escape(value)); | |
1006 | first = false; | |
db31c358 | 1007 | } |
ec1f3444 NR |
1008 | } |
1009 | ||
db31c358 | 1010 | if (array) { |
d5026c09 | 1011 | builder.append("\n# (This item accepts a list of ^escaped comma-separated values)"); |
ec1f3444 NR |
1012 | } |
1013 | } | |
1014 | ||
ec1f3444 NR |
1015 | return builder.toString(); |
1016 | } | |
1017 | ||
1018 | /** | |
1019 | * The display name used in the <tt>.properties file</tt>. | |
1020 | * | |
1021 | * @return the name | |
1022 | */ | |
1023 | protected String getBundleDisplayName() { | |
db31c358 | 1024 | return keyType.toString(); |
ec1f3444 NR |
1025 | } |
1026 | ||
1027 | /** | |
1028 | * Write the header found in the configuration <tt>.properties</tt> file of | |
1029 | * this {@link Bundles}. | |
1030 | * | |
1031 | * @param writer | |
1032 | * the {@link Writer} to write the header in | |
1033 | * | |
1034 | * @throws IOException | |
1035 | * in case of IO error | |
1036 | */ | |
1037 | protected void writeHeader(Writer writer) throws IOException { | |
1038 | writer.write("# " + getBundleDisplayName() + "\n"); | |
1039 | writer.write("#\n"); | |
1040 | } | |
1041 | ||
1042 | /** | |
b15a4985 NR |
1043 | * Write the given data to the config file, i.e., "MY_ID = my_curent_value" |
1044 | * followed by a new line. | |
1045 | * <p> | |
1046 | * Will prepend a # sign if the is is not set (see | |
1047 | * {@link Bundle#isSet(Enum, boolean)}). | |
ec1f3444 NR |
1048 | * |
1049 | * @param writer | |
1050 | * the {@link Writer} to write into | |
1051 | * @param id | |
1052 | * the id to write | |
1053 | * | |
1054 | * @throws IOException | |
1055 | * in case of IO error | |
1056 | */ | |
1057 | protected void writeValue(Writer writer, E id) throws IOException { | |
b15a4985 NR |
1058 | boolean set = isSet(id, false); |
1059 | writeValue(writer, id.name(), getString(id), set); | |
ec1f3444 NR |
1060 | } |
1061 | ||
1062 | /** | |
1063 | * Write the given data to the config file, i.e., "MY_ID = my_curent_value" | |
b15a4985 NR |
1064 | * followed by a new line. |
1065 | * <p> | |
1066 | * Will prepend a # sign if the is is not set. | |
ec1f3444 NR |
1067 | * |
1068 | * @param writer | |
1069 | * the {@link Writer} to write into | |
1070 | * @param id | |
1071 | * the id to write | |
1072 | * @param value | |
1073 | * the id's value | |
b15a4985 NR |
1074 | * @param set |
1075 | * the value is set in this {@link Bundle} | |
ec1f3444 NR |
1076 | * |
1077 | * @throws IOException | |
1078 | * in case of IO error | |
1079 | */ | |
b15a4985 NR |
1080 | protected void writeValue(Writer writer, String id, String value, |
1081 | boolean set) throws IOException { | |
1082 | ||
1083 | if (!set) { | |
1084 | writer.write('#'); | |
1085 | } | |
1086 | ||
ec1f3444 NR |
1087 | writer.write(id); |
1088 | writer.write(" = "); | |
1089 | ||
1090 | if (value == null) { | |
1091 | value = ""; | |
1092 | } | |
1093 | ||
009196a4 | 1094 | String[] lines = value.replaceAll("\t", "\\\\\\t").split("\n"); |
ec1f3444 NR |
1095 | for (int i = 0; i < lines.length; i++) { |
1096 | writer.write(lines[i]); | |
1097 | if (i < lines.length - 1) { | |
1098 | writer.write("\\n\\"); | |
1099 | } | |
1100 | writer.write("\n"); | |
1101 | } | |
1102 | } | |
1103 | ||
1104 | /** | |
1105 | * Return the source file for this {@link Bundles} from the given path. | |
1106 | * | |
1107 | * @param path | |
1108 | * the path where the .properties files are | |
1109 | * | |
1110 | * @return the source {@link File} | |
ec1f3444 NR |
1111 | */ |
1112 | protected File getUpdateFile(String path) { | |
db31c358 | 1113 | return new File(path, keyType.name() + ".properties"); |
ec1f3444 NR |
1114 | } |
1115 | ||
1116 | /** | |
62c9ec78 | 1117 | * Change the currently used bundle, and reset all changes. |
ec1f3444 NR |
1118 | * |
1119 | * @param name | |
1120 | * the name of the bundle to load | |
1121 | * @param locale | |
1122 | * the {@link Locale} to use | |
e9ca6bb8 NR |
1123 | * @param resetToDefault |
1124 | * reset to the default configuration (do not look into the | |
1125 | * possible user configuration files, only take the original | |
1126 | * configuration) | |
ec1f3444 | 1127 | */ |
e9ca6bb8 | 1128 | protected void setBundle(Enum<?> name, Locale locale, boolean resetToDefault) { |
62c9ec78 | 1129 | changeMap.clear(); |
ec1f3444 | 1130 | String dir = Bundles.getDirectory(); |
e8aa5bf9 | 1131 | String bname = type.getPackage().getName() + "." + name.name(); |
ec1f3444 | 1132 | |
487926f7 | 1133 | boolean found = false; |
e9ca6bb8 | 1134 | if (!resetToDefault && dir != null) { |
e8aa5bf9 | 1135 | // Look into Bundles.getDirectory() for .properties files |
ec1f3444 NR |
1136 | try { |
1137 | File file = getPropertyFile(dir, name.name(), locale); | |
1138 | if (file != null) { | |
1139 | Reader reader = new InputStreamReader(new FileInputStream( | |
b15a4985 | 1140 | file), "UTF-8"); |
487926f7 NR |
1141 | resetMap(new PropertyResourceBundle(reader)); |
1142 | found = true; | |
ec1f3444 NR |
1143 | } |
1144 | } catch (IOException e) { | |
1145 | e.printStackTrace(); | |
1146 | } | |
1147 | } | |
1148 | ||
487926f7 | 1149 | if (!found) { |
e8aa5bf9 | 1150 | // Look into the package itself for resources |
e9ca6bb8 | 1151 | try { |
487926f7 NR |
1152 | resetMap(ResourceBundle |
1153 | .getBundle(bname, locale, type.getClassLoader(), | |
1154 | new FixedResourceBundleControl())); | |
e8aa5bf9 NR |
1155 | found = true; |
1156 | } catch (MissingResourceException e) { | |
e9ca6bb8 | 1157 | } catch (Exception e) { |
e8aa5bf9 | 1158 | e.printStackTrace(); |
487926f7 NR |
1159 | } |
1160 | } | |
e8aa5bf9 NR |
1161 | |
1162 | if (!found) { | |
1163 | // We have no bundle for this Bundle | |
1164 | System.err.println("No bundle found for: " + bname); | |
1165 | resetMap(null); | |
1166 | } | |
487926f7 NR |
1167 | } |
1168 | ||
1169 | /** | |
e8aa5bf9 | 1170 | * Reset the backing map to the content of the given bundle, or with default |
b15a4985 | 1171 | * values if bundle is NULL. |
487926f7 NR |
1172 | * |
1173 | * @param bundle | |
1174 | * the bundle to copy | |
1175 | */ | |
b771aed5 | 1176 | protected void resetMap(ResourceBundle bundle) { |
487926f7 | 1177 | this.map.clear(); |
e8aa5bf9 NR |
1178 | for (Field field : type.getDeclaredFields()) { |
1179 | try { | |
1180 | Meta meta = field.getAnnotation(Meta.class); | |
1181 | if (meta != null) { | |
1182 | E id = Enum.valueOf(type, field.getName()); | |
1183 | ||
1184 | String value; | |
1185 | if (bundle != null) { | |
1186 | value = bundle.getString(id.name()); | |
1187 | } else { | |
1188 | value = meta.def(); | |
1189 | } | |
1190 | ||
1191 | this.map.put(id.name(), value == null ? null : value.trim()); | |
487926f7 | 1192 | } |
e8aa5bf9 | 1193 | } catch (MissingResourceException e) { |
e9ca6bb8 NR |
1194 | } |
1195 | } | |
1196 | } | |
cd0c27d2 | 1197 | |
e9ca6bb8 NR |
1198 | /** |
1199 | * Take a snapshot of the changes in memory in this {@link Bundle} made by | |
1200 | * the "set" methods ( {@link Bundle#setString(Enum, String)}...) at the | |
1201 | * current time. | |
1202 | * | |
487926f7 | 1203 | * @return a snapshot to use with {@link Bundle#restoreSnapshot(Object)} |
e9ca6bb8 | 1204 | */ |
487926f7 | 1205 | public Object takeSnapshot() { |
e9ca6bb8 NR |
1206 | return new HashMap<String, String>(changeMap); |
1207 | } | |
1208 | ||
1209 | /** | |
1210 | * Restore a snapshot taken with {@link Bundle}, or reset the current | |
1211 | * changes if the snapshot is NULL. | |
1212 | * | |
1213 | * @param snap | |
1214 | * the snapshot or NULL | |
1215 | */ | |
1216 | @SuppressWarnings("unchecked") | |
487926f7 | 1217 | public void restoreSnapshot(Object snap) { |
e9ca6bb8 NR |
1218 | if (snap == null) { |
1219 | changeMap.clear(); | |
1220 | } else { | |
1221 | if (snap instanceof Map) { | |
1222 | changeMap = (Map<String, String>) snap; | |
1223 | } else { | |
d827da2a | 1224 | throw new RuntimeException( |
e9ca6bb8 NR |
1225 | "Restoring changes in a Bundle must be done on a changes snapshot, " |
1226 | + "or NULL to discard current changes"); | |
1227 | } | |
ec1f3444 NR |
1228 | } |
1229 | } | |
1230 | ||
1231 | /** | |
1232 | * Return the resource file that is closer to the {@link Locale}. | |
1233 | * | |
1234 | * @param dir | |
db31c358 | 1235 | * the directory to look into |
ec1f3444 | 1236 | * @param name |
db31c358 | 1237 | * the file base name (without <tt>.properties</tt>) |
ec1f3444 NR |
1238 | * @param locale |
1239 | * the {@link Locale} | |
1240 | * | |
1241 | * @return the closest match or NULL if none | |
1242 | */ | |
1243 | private File getPropertyFile(String dir, String name, Locale locale) { | |
1244 | List<String> locales = new ArrayList<String>(); | |
1245 | if (locale != null) { | |
1246 | String country = locale.getCountry() == null ? "" : locale | |
1247 | .getCountry(); | |
1248 | String language = locale.getLanguage() == null ? "" : locale | |
1249 | .getLanguage(); | |
1250 | if (!language.isEmpty() && !country.isEmpty()) { | |
1251 | locales.add("_" + language + "-" + country); | |
1252 | } | |
1253 | if (!language.isEmpty()) { | |
1254 | locales.add("_" + language); | |
1255 | } | |
1256 | } | |
1257 | ||
1258 | locales.add(""); | |
1259 | ||
1260 | File file = null; | |
1261 | for (String loc : locales) { | |
1262 | file = new File(dir, name + loc + ".properties"); | |
1263 | if (file.exists()) { | |
1264 | break; | |
ec1f3444 | 1265 | } |
d827da2a | 1266 | |
cd0c27d2 | 1267 | file = null; |
ec1f3444 NR |
1268 | } |
1269 | ||
1270 | return file; | |
1271 | } | |
1272 | } |