Commit | Line | Data |
---|---|---|
9e834013 NR |
1 | package be.nikiroo.utils.resources; |
2 | ||
3 | import java.util.ArrayList; | |
0877d6f5 | 4 | import java.util.Iterator; |
9e834013 NR |
5 | import java.util.List; |
6 | ||
7 | import be.nikiroo.utils.resources.Meta.Format; | |
8 | ||
9 | /** | |
10 | * A graphical item that reflect a configuration option from the given | |
11 | * {@link Bundle}. | |
12 | * | |
13 | * @author niki | |
14 | * | |
15 | * @param <E> | |
16 | * the type of {@link Bundle} to edit | |
17 | */ | |
0877d6f5 | 18 | public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> { |
9e834013 NR |
19 | private final Bundle<E> bundle; |
20 | private final E id; | |
21 | ||
22 | private Meta meta; | |
0877d6f5 | 23 | private List<MetaInfo<E>> children = new ArrayList<MetaInfo<E>>(); |
9e834013 NR |
24 | |
25 | private String value; | |
8517b60c NR |
26 | private List<Runnable> reloadedListeners = new ArrayList<Runnable>(); |
27 | private List<Runnable> saveListeners = new ArrayList<Runnable>(); | |
9e834013 NR |
28 | |
29 | private String name; | |
30 | private String description; | |
31 | ||
d5026c09 NR |
32 | private boolean dirty; |
33 | ||
76b51de9 NR |
34 | /** |
35 | * Create a new {@link MetaInfo} from a value (without children). | |
36 | * <p> | |
37 | * For instance, you can call | |
38 | * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>. | |
39 | * | |
40 | * @param type | |
41 | * the type of enum the value is | |
42 | * @param bundle | |
43 | * the bundle this value belongs to | |
44 | * @param id | |
45 | * the value itself | |
46 | */ | |
9e834013 NR |
47 | public MetaInfo(Class<E> type, Bundle<E> bundle, E id) { |
48 | this.bundle = bundle; | |
49 | this.id = id; | |
50 | ||
51 | try { | |
52 | this.meta = type.getDeclaredField(id.name()).getAnnotation( | |
53 | Meta.class); | |
54 | } catch (NoSuchFieldException e) { | |
55 | } catch (SecurityException e) { | |
56 | } | |
57 | ||
58 | // We consider that if a description bundle is used, everything is in it | |
59 | ||
60 | String description = null; | |
61 | if (bundle.getDescriptionBundle() != null) { | |
62 | description = bundle.getDescriptionBundle().getString(id); | |
63 | if (description != null && description.trim().isEmpty()) { | |
64 | description = null; | |
65 | } | |
66 | } | |
9e834013 NR |
67 | if (description == null) { |
68 | description = meta.description(); | |
0877d6f5 NR |
69 | if (description == null) { |
70 | description = ""; | |
71 | } | |
d18e136e NR |
72 | } |
73 | ||
74 | String name = idToName(id, null); | |
75 | ||
76 | // Special rules for groups: | |
77 | if (meta.group()) { | |
78 | String groupName = description.split("\n")[0]; | |
79 | description = description.substring(groupName.length()).trim(); | |
80 | if (!groupName.isEmpty()) { | |
81 | name = groupName; | |
9e834013 NR |
82 | } |
83 | } | |
84 | ||
d18e136e NR |
85 | if (meta.def() != null && !meta.def().isEmpty()) { |
86 | if (!description.isEmpty()) { | |
87 | description += "\n\n"; | |
88 | } | |
89 | description += "(Default value: " + meta.def() + ")"; | |
9e834013 NR |
90 | } |
91 | ||
92 | this.name = name; | |
93 | this.description = description; | |
94 | ||
95 | reload(); | |
96 | } | |
97 | ||
98 | /** | |
d18e136e NR |
99 | * For normal items, this is the name of this item, deduced from its ID (or |
100 | * in other words, it is the ID but presented in a displayable form). | |
101 | * <p> | |
102 | * For group items, this is the first line of the description if it is not | |
103 | * empty (else, it is the ID in the same way as normal items). | |
9e834013 | 104 | * <p> |
d18e136e | 105 | * Never NULL. |
9e834013 | 106 | * |
d18e136e NR |
107 | * |
108 | * @return the name, never NULL | |
9e834013 NR |
109 | */ |
110 | public String getName() { | |
111 | return name; | |
112 | } | |
113 | ||
114 | /** | |
d18e136e NR |
115 | * A description for this item: what it is or does, how to explain that item |
116 | * to the user including what can be used here (i.e., %s = file name, %d = | |
117 | * file size...). | |
118 | * <p> | |
119 | * For group, the first line ('\\n'-separated) will be used as a title while | |
120 | * the rest will be the description. | |
121 | * <p> | |
122 | * If a default value is known, it will be specified here, too. | |
123 | * <p> | |
124 | * Never NULL. | |
9e834013 | 125 | * |
d18e136e | 126 | * @return the description, not NULL |
9e834013 NR |
127 | */ |
128 | public String getDescription() { | |
129 | return description; | |
130 | } | |
131 | ||
76b51de9 NR |
132 | /** |
133 | * The format this item is supposed to follow | |
134 | * | |
135 | * @return the format | |
136 | */ | |
9e834013 NR |
137 | public Format getFormat() { |
138 | return meta.format(); | |
139 | } | |
140 | ||
76b51de9 NR |
141 | /** |
142 | * The allowed list of values that a {@link Format#FIXED_LIST} item is | |
143 | * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} | |
0f7de31e | 144 | * items. Also works for {@link Format#LOCALE}. |
d18e136e NR |
145 | * <p> |
146 | * Will always allow an empty string in addition to the rest. | |
76b51de9 NR |
147 | * |
148 | * @return the list of values | |
149 | */ | |
0877d6f5 | 150 | public String[] getAllowedValues() { |
d18e136e NR |
151 | String[] list = meta.list(); |
152 | ||
153 | String[] withEmpty = new String[list.length + 1]; | |
154 | withEmpty[0] = ""; | |
155 | for (int i = 0; i < list.length; i++) { | |
156 | withEmpty[i + 1] = list[i]; | |
157 | } | |
158 | ||
159 | return withEmpty; | |
0877d6f5 NR |
160 | } |
161 | ||
0f7de31e NR |
162 | /** |
163 | * Return all the languages known by the program for this bundle. | |
164 | * <p> | |
165 | * This only works for {@link TransBundle}, and will return an empty list if | |
166 | * this is not a {@link TransBundle}. | |
167 | * | |
168 | * @return the known language codes | |
169 | */ | |
170 | public List<String> getKnownLanguages() { | |
171 | if (bundle instanceof TransBundle) { | |
172 | return ((TransBundle<E>) bundle).getKnownLanguages(); | |
173 | } | |
174 | ||
175 | return new ArrayList<String>(); | |
176 | } | |
177 | ||
76b51de9 NR |
178 | /** |
179 | * This item is a comma-separated list of values instead of a single value. | |
180 | * <p> | |
181 | * The list items are separated by a comma, each surrounded by | |
182 | * double-quotes, with backslashes and double-quotes escaped by a backslash. | |
183 | * <p> | |
184 | * Example: <tt>"un", "deux"</tt> | |
185 | * | |
186 | * @return TRUE if it is | |
187 | */ | |
0877d6f5 NR |
188 | public boolean isArray() { |
189 | return meta.array(); | |
190 | } | |
191 | ||
d5026c09 | 192 | /** |
74d2a495 NR |
193 | * A manual flag to specify if the data has been changed or not, which can |
194 | * be used by {@link MetaInfo#save(boolean)}. | |
d5026c09 NR |
195 | * |
196 | * @return TRUE if it is dirty (if it has changed) | |
197 | */ | |
198 | public boolean isDirty() { | |
199 | return dirty; | |
200 | } | |
201 | ||
202 | /** | |
74d2a495 NR |
203 | * A manual flag to specify that the data has been changed, which can be |
204 | * used by {@link MetaInfo#save(boolean)}. | |
d5026c09 NR |
205 | */ |
206 | public void setDirty() { | |
207 | this.dirty = true; | |
208 | } | |
209 | ||
210 | /** | |
211 | * The number of items in this item if it {@link MetaInfo#isArray()}, or -1 | |
212 | * if not. | |
213 | * | |
214 | * @param useDefaultIfEmpty | |
215 | * check the size of the default list instead if the list is | |
216 | * empty | |
217 | * | |
218 | * @return -1 or the number of items | |
219 | */ | |
220 | public int getListSize(boolean useDefaultIfEmpty) { | |
221 | if (!isArray()) { | |
222 | return -1; | |
223 | } | |
224 | ||
225 | return BundleHelper.getListSize(getString(-1, useDefaultIfEmpty)); | |
226 | } | |
227 | ||
76b51de9 NR |
228 | /** |
229 | * This item is only used as a group, not as an option. | |
230 | * <p> | |
231 | * For instance, you could have LANGUAGE_CODE as a group for which you won't | |
232 | * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN | |
233 | * inside for which the value must be set. | |
234 | * | |
235 | * @return TRUE if it is a group | |
236 | */ | |
237 | public boolean isGroup() { | |
238 | return meta.group(); | |
239 | } | |
240 | ||
9e834013 NR |
241 | /** |
242 | * The value stored by this item, as a {@link String}. | |
243 | * | |
d5026c09 NR |
244 | * @param item |
245 | * the item number to get for an array of values, or -1 to get | |
246 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
247 | * is FALSE) | |
d18e136e NR |
248 | * @param useDefaultIfEmpty |
249 | * use the default value instead of NULL if the setting is not | |
250 | * set | |
251 | * | |
9e834013 NR |
252 | * @return the value |
253 | */ | |
d5026c09 NR |
254 | public String getString(int item, boolean useDefaultIfEmpty) { |
255 | if (isArray() && item >= 0) { | |
256 | List<String> values = BundleHelper.parseList(value, -1); | |
257 | if (values != null && item < values.size()) { | |
258 | return values.get(item); | |
259 | } | |
260 | ||
261 | if (useDefaultIfEmpty) { | |
262 | return getDefaultString(item); | |
263 | } | |
264 | ||
265 | return null; | |
266 | } | |
267 | ||
d18e136e | 268 | if (value == null && useDefaultIfEmpty) { |
d5026c09 | 269 | return getDefaultString(item); |
d18e136e NR |
270 | } |
271 | ||
9e834013 NR |
272 | return value; |
273 | } | |
274 | ||
76b51de9 NR |
275 | /** |
276 | * The default value of this item, as a {@link String}. | |
277 | * | |
d5026c09 NR |
278 | * @param item |
279 | * the item number to get for an array of values, or -1 to get | |
280 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
281 | * is FALSE) | |
282 | * | |
76b51de9 NR |
283 | * @return the default value |
284 | */ | |
d5026c09 NR |
285 | public String getDefaultString(int item) { |
286 | if (isArray() && item >= 0) { | |
287 | List<String> values = BundleHelper.parseList(meta.def(), item); | |
288 | if (values != null && item < values.size()) { | |
289 | return values.get(item); | |
290 | } | |
291 | ||
292 | return null; | |
293 | } | |
294 | ||
9e834013 NR |
295 | return meta.def(); |
296 | } | |
297 | ||
76b51de9 NR |
298 | /** |
299 | * The value stored by this item, as a {@link Boolean}. | |
300 | * | |
d5026c09 NR |
301 | * @param item |
302 | * the item number to get for an array of values, or -1 to get | |
303 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
304 | * is FALSE) | |
d18e136e NR |
305 | * @param useDefaultIfEmpty |
306 | * use the default value instead of NULL if the setting is not | |
307 | * set | |
308 | * | |
76b51de9 NR |
309 | * @return the value |
310 | */ | |
d5026c09 NR |
311 | public Boolean getBoolean(int item, boolean useDefaultIfEmpty) { |
312 | return BundleHelper | |
313 | .parseBoolean(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
314 | } |
315 | ||
76b51de9 NR |
316 | /** |
317 | * The default value of this item, as a {@link Boolean}. | |
318 | * | |
d5026c09 NR |
319 | * @param item |
320 | * the item number to get for an array of values, or -1 to get | |
321 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
322 | * is FALSE) | |
323 | * | |
76b51de9 NR |
324 | * @return the default value |
325 | */ | |
d5026c09 NR |
326 | public Boolean getDefaultBoolean(int item) { |
327 | return BundleHelper.parseBoolean(getDefaultString(item), -1); | |
9e834013 NR |
328 | } |
329 | ||
76b51de9 NR |
330 | /** |
331 | * The value stored by this item, as a {@link Character}. | |
332 | * | |
d5026c09 NR |
333 | * @param item |
334 | * the item number to get for an array of values, or -1 to get | |
335 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
336 | * is FALSE) | |
d18e136e NR |
337 | * @param useDefaultIfEmpty |
338 | * use the default value instead of NULL if the setting is not | |
339 | * set | |
340 | * | |
76b51de9 NR |
341 | * @return the value |
342 | */ | |
d5026c09 NR |
343 | public Character getCharacter(int item, boolean useDefaultIfEmpty) { |
344 | return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty), | |
345 | -1); | |
9e834013 NR |
346 | } |
347 | ||
76b51de9 NR |
348 | /** |
349 | * The default value of this item, as a {@link Character}. | |
350 | * | |
d5026c09 NR |
351 | * @param item |
352 | * the item number to get for an array of values, or -1 to get | |
353 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
354 | * is FALSE) | |
355 | * | |
76b51de9 NR |
356 | * @return the default value |
357 | */ | |
d5026c09 NR |
358 | public Character getDefaultCharacter(int item) { |
359 | return BundleHelper.parseCharacter(getDefaultString(item), -1); | |
9e834013 NR |
360 | } |
361 | ||
76b51de9 NR |
362 | /** |
363 | * The value stored by this item, as an {@link Integer}. | |
364 | * | |
d5026c09 NR |
365 | * @param item |
366 | * the item number to get for an array of values, or -1 to get | |
367 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
368 | * is FALSE) | |
d18e136e NR |
369 | * @param useDefaultIfEmpty |
370 | * use the default value instead of NULL if the setting is not | |
371 | * set | |
372 | * | |
76b51de9 NR |
373 | * @return the value |
374 | */ | |
d5026c09 NR |
375 | public Integer getInteger(int item, boolean useDefaultIfEmpty) { |
376 | return BundleHelper | |
377 | .parseInteger(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
378 | } |
379 | ||
76b51de9 NR |
380 | /** |
381 | * The default value of this item, as an {@link Integer}. | |
382 | * | |
d5026c09 NR |
383 | * @param item |
384 | * the item number to get for an array of values, or -1 to get | |
385 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
386 | * is FALSE) | |
387 | * | |
76b51de9 NR |
388 | * @return the default value |
389 | */ | |
d5026c09 NR |
390 | public Integer getDefaultInteger(int item) { |
391 | return BundleHelper.parseInteger(getDefaultString(item), -1); | |
9e834013 NR |
392 | } |
393 | ||
76b51de9 NR |
394 | /** |
395 | * The value stored by this item, as a colour (represented here as an | |
396 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
397 | * <p> | |
398 | * The returned colour value is an ARGB value. | |
399 | * | |
d5026c09 NR |
400 | * @param item |
401 | * the item number to get for an array of values, or -1 to get | |
402 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
403 | * is FALSE) | |
d18e136e NR |
404 | * @param useDefaultIfEmpty |
405 | * use the default value instead of NULL if the setting is not | |
406 | * set | |
407 | * | |
76b51de9 NR |
408 | * @return the value |
409 | */ | |
d5026c09 NR |
410 | public Integer getColor(int item, boolean useDefaultIfEmpty) { |
411 | return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
412 | } |
413 | ||
76b51de9 NR |
414 | /** |
415 | * The default value stored by this item, as a colour (represented here as | |
416 | * an {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
417 | * <p> | |
418 | * The returned colour value is an ARGB value. | |
419 | * | |
d5026c09 NR |
420 | * @param item |
421 | * the item number to get for an array of values, or -1 to get | |
422 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
423 | * is FALSE) | |
424 | * | |
76b51de9 NR |
425 | * @return the value |
426 | */ | |
d5026c09 NR |
427 | public Integer getDefaultColor(int item) { |
428 | return BundleHelper.parseColor(getDefaultString(item), -1); | |
9e834013 NR |
429 | } |
430 | ||
76b51de9 NR |
431 | /** |
432 | * A {@link String} representation of the list of values. | |
433 | * <p> | |
434 | * The list of values is comma-separated and each value is surrounded by | |
435 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
436 | * | |
d5026c09 NR |
437 | * @param item |
438 | * the item number to get for an array of values, or -1 to get | |
439 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
440 | * is FALSE) | |
d18e136e NR |
441 | * @param useDefaultIfEmpty |
442 | * use the default value instead of NULL if the setting is not | |
443 | * set | |
444 | * | |
76b51de9 NR |
445 | * @return the value |
446 | */ | |
d5026c09 NR |
447 | public List<String> getList(int item, boolean useDefaultIfEmpty) { |
448 | return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
449 | } |
450 | ||
76b51de9 NR |
451 | /** |
452 | * A {@link String} representation of the default list of values. | |
453 | * <p> | |
454 | * The list of values is comma-separated and each value is surrounded by | |
455 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
456 | * | |
d5026c09 NR |
457 | * @param item |
458 | * the item number to get for an array of values, or -1 to get | |
459 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
460 | * is FALSE) | |
461 | * | |
76b51de9 NR |
462 | * @return the value |
463 | */ | |
d5026c09 NR |
464 | public List<String> getDefaultList(int item) { |
465 | return BundleHelper.parseList(getDefaultString(item), -1); | |
9e834013 NR |
466 | } |
467 | ||
468 | /** | |
469 | * The value stored by this item, as a {@link String}. | |
470 | * | |
471 | * @param value | |
472 | * the new value | |
d5026c09 NR |
473 | * @param item |
474 | * the item number to set for an array of values, or -1 to set | |
475 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
476 | * is FALSE) | |
9e834013 | 477 | */ |
d5026c09 NR |
478 | public void setString(String value, int item) { |
479 | if (isArray() && item >= 0) { | |
856f5898 | 480 | this.value = BundleHelper.fromList(this.value, value, item); |
d5026c09 NR |
481 | } else { |
482 | this.value = value; | |
483 | } | |
9e834013 NR |
484 | } |
485 | ||
76b51de9 NR |
486 | /** |
487 | * The value stored by this item, as a {@link Boolean}. | |
488 | * | |
489 | * @param value | |
490 | * the new value | |
d5026c09 NR |
491 | * @param item |
492 | * the item number to set for an array of values, or -1 to set | |
493 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
494 | * is FALSE) | |
76b51de9 | 495 | */ |
d5026c09 NR |
496 | public void setBoolean(boolean value, int item) { |
497 | setString(BundleHelper.fromBoolean(value), item); | |
9e834013 NR |
498 | } |
499 | ||
76b51de9 NR |
500 | /** |
501 | * The value stored by this item, as a {@link Character}. | |
502 | * | |
503 | * @param value | |
504 | * the new value | |
d5026c09 NR |
505 | * @param item |
506 | * the item number to set for an array of values, or -1 to set | |
507 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
508 | * is FALSE) | |
76b51de9 | 509 | */ |
d5026c09 NR |
510 | public void setCharacter(char value, int item) { |
511 | setString(BundleHelper.fromCharacter(value), item); | |
9e834013 NR |
512 | } |
513 | ||
76b51de9 NR |
514 | /** |
515 | * The value stored by this item, as an {@link Integer}. | |
516 | * | |
517 | * @param value | |
518 | * the new value | |
d5026c09 NR |
519 | * @param item |
520 | * the item number to set for an array of values, or -1 to set | |
521 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
522 | * is FALSE) | |
76b51de9 | 523 | */ |
d5026c09 NR |
524 | public void setInteger(int value, int item) { |
525 | setString(BundleHelper.fromInteger(value), item); | |
9e834013 NR |
526 | } |
527 | ||
76b51de9 NR |
528 | /** |
529 | * The value stored by this item, as a colour (represented here as an | |
530 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
531 | * <p> | |
74d2a495 | 532 | * The colour value is an ARGB value. |
76b51de9 NR |
533 | * |
534 | * @param value | |
535 | * the value | |
d5026c09 NR |
536 | * @param item |
537 | * the item number to set for an array of values, or -1 to set | |
538 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
539 | * is FALSE) | |
76b51de9 | 540 | */ |
d5026c09 NR |
541 | public void setColor(int value, int item) { |
542 | setString(BundleHelper.fromColor(value), item); | |
9e834013 NR |
543 | } |
544 | ||
76b51de9 NR |
545 | /** |
546 | * A {@link String} representation of the default list of values. | |
547 | * <p> | |
548 | * The list of values is comma-separated and each value is surrounded by | |
549 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
550 | * | |
551 | * @param value | |
552 | * the {@link String} representation | |
d5026c09 NR |
553 | * @param item |
554 | * the item number to set for an array of values, or -1 to set | |
555 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
556 | * is FALSE) | |
76b51de9 | 557 | */ |
d5026c09 NR |
558 | public void setList(List<String> value, int item) { |
559 | setString(BundleHelper.fromList(value), item); | |
9e834013 NR |
560 | } |
561 | ||
562 | /** | |
76b51de9 NR |
563 | * Reload the value from the {@link Bundle}, so the last value that was |
564 | * saved will be used. | |
9e834013 NR |
565 | */ |
566 | public void reload() { | |
fde375c1 NR |
567 | if (bundle.isSet(id, false)) { |
568 | value = bundle.getString(id); | |
569 | } else { | |
570 | value = null; | |
571 | } | |
572 | ||
856f5898 NR |
573 | // Copy the list so we can create new listener in a listener |
574 | for (Runnable listener : new ArrayList<Runnable>(reloadedListeners)) { | |
9e834013 NR |
575 | try { |
576 | listener.run(); | |
577 | } catch (Exception e) { | |
9e834013 NR |
578 | e.printStackTrace(); |
579 | } | |
580 | } | |
581 | } | |
582 | ||
76b51de9 NR |
583 | /** |
584 | * Add a listener that will be called <b>after</b> a reload operation. | |
585 | * <p> | |
586 | * You could use it to refresh the UI for instance. | |
587 | * | |
588 | * @param listener | |
589 | * the listener | |
590 | */ | |
8517b60c NR |
591 | public void addReloadedListener(Runnable listener) { |
592 | reloadedListeners.add(listener); | |
9e834013 NR |
593 | } |
594 | ||
595 | /** | |
596 | * Save the current value to the {@link Bundle}. | |
d5026c09 NR |
597 | * <p> |
598 | * Note that listeners will be called <b>before</b> the dirty check and | |
599 | * <b>before</b> saving the value. | |
600 | * | |
601 | * @param onlyIfDirty | |
602 | * only save the data if the dirty flag is set (will reset the | |
603 | * dirty flag) | |
9e834013 | 604 | */ |
d5026c09 | 605 | public void save(boolean onlyIfDirty) { |
856f5898 NR |
606 | // Copy the list so we can create new listener in a listener |
607 | for (Runnable listener : new ArrayList<Runnable>(saveListeners)) { | |
8517b60c NR |
608 | try { |
609 | listener.run(); | |
610 | } catch (Exception e) { | |
8517b60c NR |
611 | e.printStackTrace(); |
612 | } | |
613 | } | |
d5026c09 NR |
614 | |
615 | if (!onlyIfDirty || isDirty()) { | |
616 | bundle.setString(id, value); | |
617 | } | |
9e834013 NR |
618 | } |
619 | ||
76b51de9 NR |
620 | /** |
621 | * Add a listener that will be called <b>before</b> a save operation. | |
622 | * <p> | |
623 | * You could use it to make some modification to the stored value before it | |
624 | * is saved. | |
625 | * | |
626 | * @param listener | |
627 | * the listener | |
628 | */ | |
8517b60c NR |
629 | public void addSaveListener(Runnable listener) { |
630 | saveListeners.add(listener); | |
631 | } | |
632 | ||
76b51de9 NR |
633 | /** |
634 | * The sub-items if any (if no sub-items, will return an empty list). | |
635 | * <p> | |
636 | * Sub-items are declared when a {@link Meta} has an ID that starts with the | |
637 | * ID of a {@link Meta#group()} {@link MetaInfo}. | |
638 | * <p> | |
639 | * For instance: | |
640 | * <ul> | |
641 | * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li> | |
642 | * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li> | |
643 | * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li> | |
644 | * </ul> | |
645 | * | |
646 | * @return the sub-items if any | |
647 | */ | |
648 | public List<MetaInfo<E>> getChildren() { | |
649 | return children; | |
650 | } | |
651 | ||
4840f1a4 NR |
652 | /** |
653 | * The number of sub-items, if any. | |
654 | * | |
655 | * @return the number or 0 | |
656 | */ | |
657 | public int size() { | |
658 | return children.size(); | |
659 | } | |
660 | ||
0877d6f5 NR |
661 | @Override |
662 | public Iterator<MetaInfo<E>> iterator() { | |
663 | return children.iterator(); | |
664 | } | |
665 | ||
9e834013 NR |
666 | /** |
667 | * Create a list of {@link MetaInfo}, one for each of the item in the given | |
668 | * {@link Bundle}. | |
669 | * | |
670 | * @param <E> | |
671 | * the type of {@link Bundle} to edit | |
672 | * @param type | |
673 | * a class instance of the item type to work on | |
674 | * @param bundle | |
675 | * the {@link Bundle} to sort through | |
676 | * | |
677 | * @return the list | |
678 | */ | |
679 | static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type, | |
680 | Bundle<E> bundle) { | |
681 | List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>(); | |
76b51de9 | 682 | List<MetaInfo<E>> shadow = new ArrayList<MetaInfo<E>>(); |
9e834013 | 683 | for (E id : type.getEnumConstants()) { |
76b51de9 NR |
684 | MetaInfo<E> info = new MetaInfo<E>(type, bundle, id); |
685 | list.add(info); | |
686 | shadow.add(info); | |
9e834013 NR |
687 | } |
688 | ||
76b51de9 NR |
689 | for (int i = 0; i < list.size(); i++) { |
690 | MetaInfo<E> info = list.get(i); | |
8517b60c | 691 | |
76b51de9 NR |
692 | MetaInfo<E> parent = findParent(info, shadow); |
693 | if (parent != null) { | |
694 | list.remove(i--); | |
695 | parent.children.add(info); | |
d18e136e | 696 | info.name = idToName(info.id, parent.id); |
8517b60c NR |
697 | } |
698 | } | |
699 | ||
76b51de9 | 700 | return list; |
8517b60c NR |
701 | } |
702 | ||
76b51de9 NR |
703 | /** |
704 | * Find the longest parent of the given {@link MetaInfo}, which means: | |
705 | * <ul> | |
706 | * <li>the parent is a {@link Meta#group()}</li> | |
707 | * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li> | |
708 | * <li>there is no other parent sharing a substring for this | |
709 | * {@link MetaInfo} with a longer Id</li> | |
710 | * </ul> | |
711 | * | |
712 | * @param <E> | |
713 | * the kind of enum | |
714 | * @param info | |
715 | * the info to look for a parent for | |
716 | * @param candidates | |
717 | * the list of potential parents | |
718 | * | |
719 | * @return the longest parent or NULL if no parent is found | |
720 | */ | |
8517b60c | 721 | static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info, |
76b51de9 NR |
722 | List<MetaInfo<E>> candidates) { |
723 | String id = info.id.toString(); | |
8517b60c NR |
724 | MetaInfo<E> group = null; |
725 | for (MetaInfo<E> pcandidate : candidates) { | |
76b51de9 NR |
726 | if (pcandidate.isGroup()) { |
727 | String candidateId = pcandidate.id.toString(); | |
728 | if (!id.equals(candidateId) && id.startsWith(candidateId)) { | |
729 | if (group == null | |
730 | || group.id.toString().length() < candidateId | |
731 | .length()) { | |
732 | group = pcandidate; | |
733 | } | |
8517b60c NR |
734 | } |
735 | } | |
736 | } | |
737 | ||
738 | return group; | |
739 | } | |
d18e136e NR |
740 | |
741 | static private <E extends Enum<E>> String idToName(E id, E prefix) { | |
742 | String name = id.toString(); | |
743 | if (prefix != null && name.startsWith(prefix.toString())) { | |
744 | name = name.substring(prefix.toString().length()); | |
745 | } | |
746 | ||
747 | if (name.length() > 0) { | |
748 | name = name.substring(0, 1).toUpperCase() | |
749 | + name.substring(1).toLowerCase(); | |
750 | } | |
751 | ||
752 | name = name.replace("_", " "); | |
753 | ||
754 | return name.trim(); | |
755 | } | |
9e834013 | 756 | } |