Improve cache + jdoc, traces
[nikiroo-utils.git] / src / be / nikiroo / utils / resources / MetaInfo.java
CommitLineData
9e834013
NR
1package be.nikiroo.utils.resources;
2
3import java.util.ArrayList;
0877d6f5 4import java.util.Iterator;
9e834013
NR
5import java.util.List;
6
7import 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 18public 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
76b51de9
NR
32 /**
33 * Create a new {@link MetaInfo} from a value (without children).
34 * <p>
35 * For instance, you can call
36 * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>.
37 *
38 * @param type
39 * the type of enum the value is
40 * @param bundle
41 * the bundle this value belongs to
42 * @param id
43 * the value itself
44 */
9e834013
NR
45 public MetaInfo(Class<E> type, Bundle<E> bundle, E id) {
46 this.bundle = bundle;
47 this.id = id;
48
49 try {
50 this.meta = type.getDeclaredField(id.name()).getAnnotation(
51 Meta.class);
52 } catch (NoSuchFieldException e) {
53 } catch (SecurityException e) {
54 }
55
56 // We consider that if a description bundle is used, everything is in it
57
58 String description = null;
59 if (bundle.getDescriptionBundle() != null) {
60 description = bundle.getDescriptionBundle().getString(id);
61 if (description != null && description.trim().isEmpty()) {
62 description = null;
63 }
64 }
9e834013
NR
65 if (description == null) {
66 description = meta.description();
0877d6f5
NR
67 if (description == null) {
68 description = "";
69 }
d18e136e
NR
70 }
71
72 String name = idToName(id, null);
73
74 // Special rules for groups:
75 if (meta.group()) {
76 String groupName = description.split("\n")[0];
77 description = description.substring(groupName.length()).trim();
78 if (!groupName.isEmpty()) {
79 name = groupName;
9e834013
NR
80 }
81 }
82
d18e136e
NR
83 if (meta.def() != null && !meta.def().isEmpty()) {
84 if (!description.isEmpty()) {
85 description += "\n\n";
86 }
87 description += "(Default value: " + meta.def() + ")";
9e834013
NR
88 }
89
90 this.name = name;
91 this.description = description;
92
93 reload();
94 }
95
96 /**
d18e136e
NR
97 * For normal items, this is the name of this item, deduced from its ID (or
98 * in other words, it is the ID but presented in a displayable form).
99 * <p>
100 * For group items, this is the first line of the description if it is not
101 * empty (else, it is the ID in the same way as normal items).
9e834013 102 * <p>
d18e136e 103 * Never NULL.
9e834013 104 *
d18e136e
NR
105 *
106 * @return the name, never NULL
9e834013
NR
107 */
108 public String getName() {
109 return name;
110 }
111
112 /**
d18e136e
NR
113 * A description for this item: what it is or does, how to explain that item
114 * to the user including what can be used here (i.e., %s = file name, %d =
115 * file size...).
116 * <p>
117 * For group, the first line ('\\n'-separated) will be used as a title while
118 * the rest will be the description.
119 * <p>
120 * If a default value is known, it will be specified here, too.
121 * <p>
122 * Never NULL.
9e834013 123 *
d18e136e 124 * @return the description, not NULL
9e834013
NR
125 */
126 public String getDescription() {
127 return description;
128 }
129
76b51de9
NR
130 /**
131 * The format this item is supposed to follow
132 *
133 * @return the format
134 */
9e834013
NR
135 public Format getFormat() {
136 return meta.format();
137 }
138
76b51de9
NR
139 /**
140 * The allowed list of values that a {@link Format#FIXED_LIST} item is
141 * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST}
142 * items.
d18e136e
NR
143 * <p>
144 * Will always allow an empty string in addition to the rest.
76b51de9
NR
145 *
146 * @return the list of values
147 */
0877d6f5 148 public String[] getAllowedValues() {
d18e136e
NR
149 String[] list = meta.list();
150
151 String[] withEmpty = new String[list.length + 1];
152 withEmpty[0] = "";
153 for (int i = 0; i < list.length; i++) {
154 withEmpty[i + 1] = list[i];
155 }
156
157 return withEmpty;
0877d6f5
NR
158 }
159
76b51de9
NR
160 /**
161 * This item is a comma-separated list of values instead of a single value.
162 * <p>
163 * The list items are separated by a comma, each surrounded by
164 * double-quotes, with backslashes and double-quotes escaped by a backslash.
165 * <p>
166 * Example: <tt>"un", "deux"</tt>
167 *
168 * @return TRUE if it is
169 */
0877d6f5
NR
170 public boolean isArray() {
171 return meta.array();
172 }
173
76b51de9
NR
174 /**
175 * This item is only used as a group, not as an option.
176 * <p>
177 * For instance, you could have LANGUAGE_CODE as a group for which you won't
178 * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN
179 * inside for which the value must be set.
180 *
181 * @return TRUE if it is a group
182 */
183 public boolean isGroup() {
184 return meta.group();
185 }
186
9e834013
NR
187 /**
188 * The value stored by this item, as a {@link String}.
189 *
d18e136e
NR
190 * @param useDefaultIfEmpty
191 * use the default value instead of NULL if the setting is not
192 * set
193 *
9e834013
NR
194 * @return the value
195 */
d18e136e
NR
196 public String getString(boolean useDefaultIfEmpty) {
197 if (value == null && useDefaultIfEmpty) {
198 return getDefaultString();
199 }
200
9e834013
NR
201 return value;
202 }
203
76b51de9
NR
204 /**
205 * The default value of this item, as a {@link String}.
206 *
207 * @return the default value
208 */
9e834013
NR
209 public String getDefaultString() {
210 return meta.def();
211 }
212
76b51de9
NR
213 /**
214 * The value stored by this item, as a {@link Boolean}.
215 *
d18e136e
NR
216 * @param useDefaultIfEmpty
217 * use the default value instead of NULL if the setting is not
218 * set
219 *
76b51de9
NR
220 * @return the value
221 */
d18e136e
NR
222 public Boolean getBoolean(boolean useDefaultIfEmpty) {
223 return BundleHelper.parseBoolean(getString(useDefaultIfEmpty));
9e834013
NR
224 }
225
76b51de9
NR
226 /**
227 * The default value of this item, as a {@link Boolean}.
228 *
229 * @return the default value
230 */
9e834013
NR
231 public Boolean getDefaultBoolean() {
232 return BundleHelper.parseBoolean(getDefaultString());
233 }
234
76b51de9
NR
235 /**
236 * The value stored by this item, as a {@link Character}.
237 *
d18e136e
NR
238 * @param useDefaultIfEmpty
239 * use the default value instead of NULL if the setting is not
240 * set
241 *
76b51de9
NR
242 * @return the value
243 */
d18e136e
NR
244 public Character getCharacter(boolean useDefaultIfEmpty) {
245 return BundleHelper.parseCharacter(getString(useDefaultIfEmpty));
9e834013
NR
246 }
247
76b51de9
NR
248 /**
249 * The default value of this item, as a {@link Character}.
250 *
251 * @return the default value
252 */
9e834013
NR
253 public Character getDefaultCharacter() {
254 return BundleHelper.parseCharacter(getDefaultString());
255 }
256
76b51de9
NR
257 /**
258 * The value stored by this item, as an {@link Integer}.
259 *
d18e136e
NR
260 * @param useDefaultIfEmpty
261 * use the default value instead of NULL if the setting is not
262 * set
263 *
76b51de9
NR
264 * @return the value
265 */
d18e136e
NR
266 public Integer getInteger(boolean useDefaultIfEmpty) {
267 return BundleHelper.parseInteger(getString(useDefaultIfEmpty));
9e834013
NR
268 }
269
76b51de9
NR
270 /**
271 * The default value of this item, as an {@link Integer}.
272 *
273 * @return the default value
274 */
9e834013
NR
275 public Integer getDefaultInteger() {
276 return BundleHelper.parseInteger(getDefaultString());
277 }
278
76b51de9
NR
279 /**
280 * The value stored by this item, as a colour (represented here as an
281 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
282 * <p>
283 * The returned colour value is an ARGB value.
284 *
d18e136e
NR
285 * @param useDefaultIfEmpty
286 * use the default value instead of NULL if the setting is not
287 * set
288 *
76b51de9
NR
289 * @return the value
290 */
d18e136e
NR
291 public Integer getColor(boolean useDefaultIfEmpty) {
292 return BundleHelper.parseColor(getString(useDefaultIfEmpty));
9e834013
NR
293 }
294
76b51de9
NR
295 /**
296 * The default value stored by this item, as a colour (represented here as
297 * an {@link Integer}) if it represents a colour, or NULL if it doesn't.
298 * <p>
299 * The returned colour value is an ARGB value.
300 *
301 * @return the value
302 */
9e834013
NR
303 public Integer getDefaultColor() {
304 return BundleHelper.parseColor(getDefaultString());
305 }
306
76b51de9
NR
307 /**
308 * A {@link String} representation of the list of values.
309 * <p>
310 * The list of values is comma-separated and each value is surrounded by
311 * double-quotes; backslashes and double-quotes are escaped by a backslash.
312 *
d18e136e
NR
313 * @param useDefaultIfEmpty
314 * use the default value instead of NULL if the setting is not
315 * set
316 *
76b51de9
NR
317 * @return the value
318 */
d18e136e
NR
319 public List<String> getList(boolean useDefaultIfEmpty) {
320 return BundleHelper.parseList(getString(useDefaultIfEmpty));
9e834013
NR
321 }
322
76b51de9
NR
323 /**
324 * A {@link String} representation of the default list of values.
325 * <p>
326 * The list of values is comma-separated and each value is surrounded by
327 * double-quotes; backslashes and double-quotes are escaped by a backslash.
328 *
329 * @return the value
330 */
9e834013
NR
331 public List<String> getDefaultList() {
332 return BundleHelper.parseList(getDefaultString());
333 }
334
335 /**
336 * The value stored by this item, as a {@link String}.
337 *
338 * @param value
339 * the new value
340 */
341 public void setString(String value) {
342 this.value = value;
343 }
344
76b51de9
NR
345 /**
346 * The value stored by this item, as a {@link Boolean}.
347 *
348 * @param value
349 * the new value
350 */
9e834013
NR
351 public void setBoolean(boolean value) {
352 setString(BundleHelper.fromBoolean(value));
353 }
354
76b51de9
NR
355 /**
356 * The value stored by this item, as a {@link Character}.
357 *
358 * @param value
359 * the new value
360 */
9e834013
NR
361 public void setCharacter(char value) {
362 setString(BundleHelper.fromCharacter(value));
363 }
364
76b51de9
NR
365 /**
366 * The value stored by this item, as an {@link Integer}.
367 *
368 * @param value
369 * the new value
370 */
9e834013
NR
371 public void setInteger(int value) {
372 setString(BundleHelper.fromInteger(value));
373 }
374
76b51de9
NR
375 /**
376 * The value stored by this item, as a colour (represented here as an
377 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
378 * <p>
379 * The returned colour value is an ARGB value.
380 *
381 * @param value
382 * the value
383 */
9e834013
NR
384 public void setColor(int value) {
385 setString(BundleHelper.fromColor(value));
386 }
387
76b51de9
NR
388 /**
389 * A {@link String} representation of the default list of values.
390 * <p>
391 * The list of values is comma-separated and each value is surrounded by
392 * double-quotes; backslashes and double-quotes are escaped by a backslash.
393 *
394 * @param value
395 * the {@link String} representation
396 *
397 */
9e834013
NR
398 public void setList(List<String> value) {
399 setString(BundleHelper.fromList(value));
400 }
401
402 /**
76b51de9
NR
403 * Reload the value from the {@link Bundle}, so the last value that was
404 * saved will be used.
9e834013
NR
405 */
406 public void reload() {
fde375c1
NR
407 if (bundle.isSet(id, false)) {
408 value = bundle.getString(id);
409 } else {
410 value = null;
411 }
412
8517b60c 413 for (Runnable listener : reloadedListeners) {
9e834013
NR
414 try {
415 listener.run();
416 } catch (Exception e) {
417 // TODO: error management?
418 e.printStackTrace();
419 }
420 }
421 }
422
76b51de9
NR
423 /**
424 * Add a listener that will be called <b>after</b> a reload operation.
425 * <p>
426 * You could use it to refresh the UI for instance.
427 *
428 * @param listener
429 * the listener
430 */
8517b60c
NR
431 public void addReloadedListener(Runnable listener) {
432 reloadedListeners.add(listener);
9e834013
NR
433 }
434
435 /**
436 * Save the current value to the {@link Bundle}.
437 */
438 public void save() {
8517b60c
NR
439 for (Runnable listener : saveListeners) {
440 try {
441 listener.run();
442 } catch (Exception e) {
443 // TODO: error management?
444 e.printStackTrace();
445 }
446 }
9e834013
NR
447 bundle.setString(id, value);
448 }
449
76b51de9
NR
450 /**
451 * Add a listener that will be called <b>before</b> a save operation.
452 * <p>
453 * You could use it to make some modification to the stored value before it
454 * is saved.
455 *
456 * @param listener
457 * the listener
458 */
8517b60c
NR
459 public void addSaveListener(Runnable listener) {
460 saveListeners.add(listener);
461 }
462
76b51de9
NR
463 /**
464 * The sub-items if any (if no sub-items, will return an empty list).
465 * <p>
466 * Sub-items are declared when a {@link Meta} has an ID that starts with the
467 * ID of a {@link Meta#group()} {@link MetaInfo}.
468 * <p>
469 * For instance:
470 * <ul>
471 * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li>
472 * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li>
473 * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li>
474 * </ul>
475 *
476 * @return the sub-items if any
477 */
478 public List<MetaInfo<E>> getChildren() {
479 return children;
480 }
481
0877d6f5
NR
482 @Override
483 public Iterator<MetaInfo<E>> iterator() {
484 return children.iterator();
485 }
486
9e834013
NR
487 /**
488 * Create a list of {@link MetaInfo}, one for each of the item in the given
489 * {@link Bundle}.
490 *
491 * @param <E>
492 * the type of {@link Bundle} to edit
493 * @param type
494 * a class instance of the item type to work on
495 * @param bundle
496 * the {@link Bundle} to sort through
497 *
498 * @return the list
499 */
500 static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type,
501 Bundle<E> bundle) {
502 List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>();
76b51de9 503 List<MetaInfo<E>> shadow = new ArrayList<MetaInfo<E>>();
9e834013 504 for (E id : type.getEnumConstants()) {
76b51de9
NR
505 MetaInfo<E> info = new MetaInfo<E>(type, bundle, id);
506 list.add(info);
507 shadow.add(info);
9e834013
NR
508 }
509
76b51de9
NR
510 for (int i = 0; i < list.size(); i++) {
511 MetaInfo<E> info = list.get(i);
8517b60c 512
76b51de9
NR
513 MetaInfo<E> parent = findParent(info, shadow);
514 if (parent != null) {
515 list.remove(i--);
516 parent.children.add(info);
d18e136e 517 info.name = idToName(info.id, parent.id);
8517b60c
NR
518 }
519 }
520
76b51de9 521 return list;
8517b60c
NR
522 }
523
76b51de9
NR
524 /**
525 * Find the longest parent of the given {@link MetaInfo}, which means:
526 * <ul>
527 * <li>the parent is a {@link Meta#group()}</li>
528 * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li>
529 * <li>there is no other parent sharing a substring for this
530 * {@link MetaInfo} with a longer Id</li>
531 * </ul>
532 *
533 * @param <E>
534 * the kind of enum
535 * @param info
536 * the info to look for a parent for
537 * @param candidates
538 * the list of potential parents
539 *
540 * @return the longest parent or NULL if no parent is found
541 */
8517b60c 542 static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info,
76b51de9
NR
543 List<MetaInfo<E>> candidates) {
544 String id = info.id.toString();
8517b60c
NR
545 MetaInfo<E> group = null;
546 for (MetaInfo<E> pcandidate : candidates) {
76b51de9
NR
547 if (pcandidate.isGroup()) {
548 String candidateId = pcandidate.id.toString();
549 if (!id.equals(candidateId) && id.startsWith(candidateId)) {
550 if (group == null
551 || group.id.toString().length() < candidateId
552 .length()) {
553 group = pcandidate;
554 }
8517b60c
NR
555 }
556 }
557 }
558
559 return group;
560 }
d18e136e
NR
561
562 static private <E extends Enum<E>> String idToName(E id, E prefix) {
563 String name = id.toString();
564 if (prefix != null && name.startsWith(prefix.toString())) {
565 name = name.substring(prefix.toString().length());
566 }
567
568 if (name.length() > 0) {
569 name = name.substring(0, 1).toUpperCase()
570 + name.substring(1).toLowerCase();
571 }
572
573 name = name.replace("_", " ");
574
575 return name.trim();
576 }
9e834013 577}