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