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) { | |
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 | } |